From e603a35f231fb7f40a422f87beb69cbf829d4f82 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 16 Apr 2018 17:00:33 -0700 Subject: [PATCH 001/316] Update version number to 2.2.0 --- version.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/version.props b/version.props index 80bf48baf5..c250b1e316 100644 --- a/version.props +++ b/version.props @@ -1,10 +1,10 @@ - 2.1.0 - preview3 + 2.2.0 + preview1 0.1.0 - alpha2 + alpha3 $(VersionPrefix) $(VersionPrefix)-$(VersionSuffix)-final From d0b0c6ae342515b6b422f13aa7d11131b06d9de0 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 20 Apr 2018 14:41:40 -0700 Subject: [PATCH 002/316] Update dependencies.props and KoreBuild version --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 8346d7ed0b..fc726ff2da 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,88 +5,88 @@ 0.9.9 0.10.13 - 2.1.0-rc1-15774 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.2.0-preview1-17037 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 5.2.4 2.8.0-beta3 2.8.0-beta3 - 2.1.0-rc1-30613 + 2.2.0-preview1-34015 1.7.0 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-26419-02 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.1.0-preview3-26413-05 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 2.0.0 - 2.1.0-rc1-26419-02 - 2.1.0-rc1-30613 - 2.1.0-rc1-30613 + 2.1.0-preview3-26413-05 + 2.2.0-preview1-34015 + 2.2.0-preview1-34015 15.6.1 4.7.49 2.0.1 1.0.1 - 4.5.0-rc1-26419-03 - 4.5.0-rc1-26419-03 - 4.5.0-rc1-26419-03 + 4.5.0-preview3-26413-02 + 4.5.0-preview3-26413-02 + 4.5.0-preview3-26413-02 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 9d4ef8c888..f27a67b442 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.1.0-rc1-15774 -commithash:ed5ca9de3c652347dbb0158a9a65eff3471d2114 +version:2.2.0-preview1-17037 +commithash:557055a86cbdc359c97d4fb1c2d23a3dc7ae731e From 1b9372f5b0c490209d49e6051f5f1b615094f399 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 23 Apr 2018 12:18:21 -0700 Subject: [PATCH 003/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 134 +++++++++++++++++++-------------------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index fc726ff2da..804e09c969 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -6,80 +6,80 @@ 0.9.9 0.10.13 2.2.0-preview1-17037 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 5.2.4 2.8.0-beta3 2.8.0-beta3 - 2.2.0-preview1-34015 + 2.2.0-preview1-34029 1.7.0 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 2.1.0-preview3-26413-05 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 2.0.0 2.1.0-preview3-26413-05 - 2.2.0-preview1-34015 - 2.2.0-preview1-34015 + 2.2.0-preview1-34029 + 2.2.0-preview1-34029 15.6.1 4.7.49 2.0.1 From b3087ab774105e4eab5fac407646968ef02c629b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Apr 2018 12:25:33 -0700 Subject: [PATCH 004/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 148 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 804e09c969..2b6bfb8aaf 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,88 +5,88 @@ 0.9.9 0.10.13 - 2.2.0-preview1-17037 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 + 2.2.0-preview1-17042 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 5.2.4 2.8.0-beta3 2.8.0-beta3 - 2.2.0-preview1-34029 + 2.2.0-preview1-34066 1.7.0 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.1.0-preview3-26413-05 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-26424-04 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 2.0.0 - 2.1.0-preview3-26413-05 - 2.2.0-preview1-34029 - 2.2.0-preview1-34029 + 2.2.0-preview1-26424-04 + 2.2.0-preview1-34066 + 2.2.0-preview1-34066 15.6.1 4.7.49 - 2.0.1 + 2.0.3 1.0.1 - 4.5.0-preview3-26413-02 - 4.5.0-preview3-26413-02 - 4.5.0-preview3-26413-02 + 4.5.0-preview3-26423-04 + 4.5.0-preview3-26423-04 + 4.5.0-preview3-26423-04 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index f27a67b442..335e579e06 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17037 -commithash:557055a86cbdc359c97d4fb1c2d23a3dc7ae731e +version:2.2.0-preview1-17042 +commithash:edf0705d014293c260de763543784330514db9a3 From 86d885e5337a7188631b6e720944fb913e38327f Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Sun, 29 Apr 2018 13:00:08 -0700 Subject: [PATCH 005/316] Added a template for filing issues --- .github/ISSUE_TEMPLATE | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE new file mode 100644 index 0000000000..d7a9ec10b7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE @@ -0,0 +1,25 @@ +#### Is this a Bug or Feature request?: + + +#### Steps to reproduce or link to a repro project: + + +#### Description of the problem: + + +#### Version of `Microsoft.AspNetCore.Mvc` or `Microsoft.AspNetCore.App` or `Microsoft.AspNetCore.All`: + + + \ No newline at end of file From 777782ac6e9cb7e574f331b883a2b927e21a31ed Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 3 May 2018 14:07:08 -0700 Subject: [PATCH 006/316] Test cleanup --- .../Internal/MvcCoreMvcOptionsSetup.cs | 4 +- .../ControllerBaseTest.cs | 10 ++-- .../Internal/ActionSelectorTest.cs | 2 +- ...thorizationApplicationModelProviderTest.cs | 2 +- ...ControllerActionDescriptorProviderTests.cs | 8 ++-- .../ControllerActionInvokerCacheTest.cs | 2 +- .../DefaultApplicationModelProviderTest.cs | 28 ++++++----- .../CorsApplicationModelProviderTest.cs | 2 +- .../TestModelMetadataProvider.cs | 4 +- .../TempDataApplicationModelProviderTest.cs | 46 ++++++++----------- 10 files changed, 53 insertions(+), 55 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs index f03e2ca661..21bb72eb27 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -77,13 +77,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal options.ValueProviderFactories.Add(new JQueryFormValueProviderFactory()); // Set up metadata providers - ConfigureAdditionalModelMetadataDetailsProvider(options.ModelMetadataDetailsProviders); + ConfigureAdditionalModelMetadataDetailsProviders(options.ModelMetadataDetailsProviders); // Set up validators options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); } - internal static void ConfigureAdditionalModelMetadataDetailsProvider(IList modelMetadataDetailsProviders) + internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList modelMetadataDetailsProviders) { // Don't bind the Type class by default as it's expensive. A user can override this behavior // by altering the collection of providers. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs index 4ff9dac6ec..f993e61803 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs @@ -2327,7 +2327,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test public async Task TryUpdateModel_FallsBackOnEmptyPrefix_IfNotSpecified() { // Arrange - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadataProvider = new EmptyModelMetadataProvider(); var valueProvider = Mock.Of(); var binder = new StubModelBinder(context => { @@ -2356,7 +2356,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test // Arrange var modelName = "mymodel"; - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadataProvider = new EmptyModelMetadataProvider(); var valueProvider = Mock.Of(); var binder = new StubModelBinder(context => { @@ -2578,7 +2578,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test // Arrange var modelName = "mymodel"; - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadataProvider = new EmptyModelMetadataProvider(); var valueProvider = Mock.Of(); var binder = new StubModelBinder(context => { @@ -2606,7 +2606,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test // Arrange var modelName = "mymodel"; - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadataProvider = new EmptyModelMetadataProvider(); var valueProvider = Mock.Of(); var binder = new StubModelBinder(context => { @@ -2834,7 +2834,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test private static ControllerBase GetController(IModelBinder binder, IValueProvider valueProvider) { - var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var metadataProvider = new EmptyModelMetadataProvider(); var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs index 5a5dbe7787..a6e1b415cc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs @@ -791,7 +791,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure var manager = GetApplicationManager(controllerTypes); - var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); + var modelProvider = new DefaultApplicationModelProvider(options, new EmptyModelMetadataProvider()); var provider = new ControllerActionDescriptorProvider( manager, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs index 5015aaa56a..866f13e461 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs @@ -169,7 +169,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { var defaultProvider = new DefaultApplicationModelProvider( Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); + new EmptyModelMetadataProvider()); var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() }); defaultProvider.OnProvidersExecuting(context); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 0afae5ae78..6fcaadbd27 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -709,7 +709,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); var options = Options.Create(new MvcOptions()); options.Value.Conventions.Add(new TestRoutingConvention()); - var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); + var modelProvider = new DefaultApplicationModelProvider(options, new EmptyModelMetadataProvider()); var provider = new ControllerActionDescriptorProvider( manager, new[] { modelProvider }, @@ -1397,7 +1397,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); - var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); + var modelProvider = new DefaultApplicationModelProvider(options, new EmptyModelMetadataProvider()); var provider = new ControllerActionDescriptorProvider( manager, @@ -1413,7 +1413,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var options = Options.Create(new MvcOptions()); var manager = GetApplicationManager(controllerTypeInfos); - var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); + var modelProvider = new DefaultApplicationModelProvider(options, new EmptyModelMetadataProvider()); var provider = new ControllerActionDescriptorProvider( manager, @@ -1432,7 +1432,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var manager = GetApplicationManager(new[] { controllerTypeInfo }); - var modelProvider = new DefaultApplicationModelProvider(options, TestModelMetadataProvider.CreateDefaultProvider()); + var modelProvider = new DefaultApplicationModelProvider(options, new EmptyModelMetadataProvider()); var provider = new ControllerActionDescriptorProvider( manager, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs index 19b1b3b171..9243142b58 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { var descriptorProvider = new CustomActionDescriptorCollectionProvider( new[] { controllerContext.ActionDescriptor }); - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var modelBinderFactory = TestModelBinderFactory.CreateDefault(); var mvcOptions = Options.Create(new MvcOptions { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs index c7f548efa8..e052aed8aa 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs @@ -40,7 +40,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void OnProvidersExecuting_AddsControllerProperties() { // Arrange - var builder = new TestApplicationModelProvider(); + var builder = new TestApplicationModelProvider( + new MvcOptions { AllowValidatingTopLevelNodes = true }, + TestModelMetadataProvider.CreateDefaultProvider()); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -84,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var detailsProvider = new BindingSourceMetadataProvider(typeof(string), BindingSource.Services); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); - var provider = new TestApplicationModelProvider(Options.Create(new MvcOptions()), modelMetadataProvider); + var provider = new TestApplicationModelProvider(new MvcOptions(), modelMetadataProvider); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -124,7 +126,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void OnProvidersExecuting_AddsBindingSources_ForActionParameters() { // Arrange - var builder = new TestApplicationModelProvider(); + var builder = new TestApplicationModelProvider( + new MvcOptions { AllowValidatingTopLevelNodes = true }, + TestModelMetadataProvider.CreateDefaultProvider()); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -166,9 +170,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_WithLegacyValidationBehavior() { // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var options = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }); - var builder = new TestApplicationModelProvider(options, modelMetadataProvider); + var builder = new TestApplicationModelProvider( + new MvcOptions(), + TestModelMetadataProvider.CreateDefaultProvider()); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -215,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var detailsProvider = new BindingSourceMetadataProvider(typeof(Guid), BindingSource.Special); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); - var provider = new TestApplicationModelProvider(Options.Create(options), modelMetadataProvider); + var provider = new TestApplicationModelProvider(options, modelMetadataProvider); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -243,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var detailsProvider = new BindingSourceMetadataProvider(typeof(Guid), BindingSource.Special); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(new[] { detailsProvider }); - var provider = new TestApplicationModelProvider(Options.Create(options), modelMetadataProvider); + var provider = new TestApplicationModelProvider(options, modelMetadataProvider); var typeInfo = typeof(ModelBinderController).GetTypeInfo(); var context = new ApplicationModelProviderContext(new[] { typeInfo }); @@ -1658,14 +1662,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal private class TestApplicationModelProvider : DefaultApplicationModelProvider { public TestApplicationModelProvider() - : this(Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }), TestModelMetadataProvider.CreateDefaultProvider()) + : this( + new MvcOptions { AllowValidatingTopLevelNodes = true }, + new EmptyModelMetadataProvider()) { } public TestApplicationModelProvider( - IOptions options, + MvcOptions options, IModelMetadataProvider modelMetadataProvider) - : base(options, modelMetadataProvider) + : base(Options.Create(options), modelMetadataProvider) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs index 3b9ab90213..3b4c88964f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs @@ -213,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var context = new ApplicationModelProviderContext(new[] { controllerType.GetTypeInfo() }); var provider = new DefaultApplicationModelProvider( Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); + new EmptyModelMetadataProvider()); provider.OnProvidersExecuting(context); return context; diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs index 73fe0c4c6b..47f6b7d2ed 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding new DataMemberRequiredBindingMetadataProvider(), }; - MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProvider(detailsProviders); + MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders); var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions())); @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding new DataMemberRequiredBindingMetadataProvider(), }; - MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProvider(detailsProviders); + MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders); detailsProviders.AddRange(providers); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs index fb6ec71dc1..ab6df27ee5 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs @@ -21,12 +21,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var type = typeof(TestController_NoTempDataProperties); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider( - Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); - var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetContext(type); // Act provider.OnProvidersExecuting(context); @@ -44,12 +40,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); var expected = $"The '{type.FullName}.Test' property with TempDataAttribute is invalid. A property using TempDataAttribute must have a public getter and setter."; - var defaultProvider = new DefaultApplicationModelProvider( - Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); - var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetContext(type); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); @@ -63,12 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var type = typeof(TestController_NullableNonPrimitiveTempDataProperty); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider( - Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); - var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetContext(type); // Act provider.OnProvidersExecuting(context); @@ -82,15 +70,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public void InitializeFilterFactory_WithExpectedPropertyHelpers_ForTempDataAttributeProperties() { // Arrange - var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2)); + var type = typeof(TestController_OneTempDataProperty); + var expected = type.GetProperty(nameof(TestController_OneTempDataProperty.Test2)); var options = Options.Create(new MvcViewOptions()); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider( - Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); - var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetContext(type); // Act provider.OnProvidersExecuting(context); @@ -110,13 +95,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal // Arrange var expected = typeof(TestController_OneTempDataProperty).GetProperty(nameof(TestController_OneTempDataProperty.Test2)); var options = Options.Create(new MvcViewOptions { SuppressTempDataAttributePrefix = true }); + var type = typeof(TestController_OneTempDataProperty); var provider = new TempDataApplicationModelProvider(options); - var defaultProvider = new DefaultApplicationModelProvider( - Options.Create(new MvcOptions()), - TestModelMetadataProvider.CreateDefaultProvider()); - - var context = new ApplicationModelProviderContext(new[] { typeof(TestController_OneTempDataProperty).GetTypeInfo() }); - defaultProvider.OnProvidersExecuting(context); + var context = GetContext(type); // Act provider.OnProvidersExecuting(context); @@ -130,6 +111,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal Assert.Equal("Test2", property.Key); } + private static ApplicationModelProviderContext GetContext(Type type) + { + var defaultProvider = new DefaultApplicationModelProvider( + Options.Create(new MvcOptions()), + new EmptyModelMetadataProvider()); + + var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); + defaultProvider.OnProvidersExecuting(context); + return context; + } + public class TestController_NoTempDataProperties { public DateTime? DateTime { get; set; } From e0188c4936f54eddbc984b8598e8cc62c72adf25 Mon Sep 17 00:00:00 2001 From: Jass Bagga Date: Fri, 4 May 2018 13:47:48 -0700 Subject: [PATCH 007/316] Modify info level logging for pages (#7733) Addresses #7602 --- .../Internal/MvcCoreLoggerExtensions.cs | 32 +++++++++++++++-- .../Internal/PageActionInvoker.cs | 2 ++ .../Internal/PageLoggerExtensions.cs | 36 +++++++++++++++++-- 3 files changed, 64 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index c840f785b1..0cee994b44 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -34,6 +34,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static readonly Action _actionExecuting; private static readonly Action _actionExecuted; + private static readonly Action _pageExecuting; + private static readonly Action _pageExecuted; + private static readonly Action _challengeResultExecuting; private static readonly Action _contentResultExecuting; @@ -158,6 +161,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal 2, "Executed action {ActionName} in {ElapsedMilliseconds}ms"); + _pageExecuting = LoggerMessage.Define( + LogLevel.Information, + 3, + "Route matched with {RouteData}. Executing page {PageName}"); + + _pageExecuted = LoggerMessage.Define( + LogLevel.Information, + 4, + "Executed page {PageName} in {ElapsedMilliseconds}ms"); + _challengeResultExecuting = LoggerMessage.Define( LogLevel.Information, 1, @@ -682,8 +695,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal stringBuilder.Append($"{routeKeys[i]} = \"{routeValues[i]}\", "); } } - - _actionExecuting(logger, stringBuilder.ToString(), action.DisplayName, null); + if (action.RouteValues["page"] != null) + { + _pageExecuting(logger, stringBuilder.ToString(), action.DisplayName, null); + } + else + { + _actionExecuting(logger, stringBuilder.ToString(), action.DisplayName, null); + } } } @@ -765,7 +784,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Don't log if logging wasn't enabled at start of request as time will be wildly wrong. if (logger.IsEnabled(LogLevel.Information)) { - _actionExecuted(logger, action.DisplayName, timeSpan.TotalMilliseconds, null); + if (action.RouteValues["page"] != null) + { + _pageExecuted(logger, action.DisplayName, timeSpan.TotalMilliseconds, null); + } + else + { + _actionExecuted(logger, action.DisplayName, timeSpan.TotalMilliseconds, null); + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs index 9684c711f0..4f91b0346e 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs @@ -275,7 +275,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Pages have an implicit 'return Page()' even without a handler method. if (_result == null) { + _logger.ExecutingImplicitHandlerMethod(_pageContext); _result = new PageResult(); + _logger.ExecutedImplicitHandlerMethod(_result); } // Ensure ViewData is set on PageResult for backwards compatibility (For example, Identity UI accesses diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs index a5d7c960d4..1ff89f5512 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs @@ -16,7 +16,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public const string PageFilter = "Page Filter"; private static readonly Action _handlerMethodExecuting; + private static readonly Action _implicitHandlerMethodExecuting; private static readonly Action _handlerMethodExecuted; + private static readonly Action _implicitHandlerMethodExecuted; private static readonly Action _pageFilterShortCircuit; private static readonly Action _malformedPageDirective; private static readonly Action _unsupportedAreaPath; @@ -34,10 +36,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal "Executing handler method {HandlerName} with arguments ({Arguments}) - ModelState is {ValidationState}"); _handlerMethodExecuted = LoggerMessage.Define( - LogLevel.Debug, + LogLevel.Information, 102, "Executed handler method {HandlerName}, returned result {ActionResult}."); + _implicitHandlerMethodExecuting = LoggerMessage.Define( + LogLevel.Information, + 103, + "Executing an implicit handler method - ModelState is {ValidationState}"); + + _implicitHandlerMethodExecuted = LoggerMessage.Define( + LogLevel.Information, + 104, + "Executed an implicit handler method, returned result {ActionResult}."); + _pageFilterShortCircuit = LoggerMessage.Define( LogLevel.Debug, 3, @@ -73,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { if (logger.IsEnabled(LogLevel.Information)) { - var handlerName = handler.MethodInfo.Name; + var handlerName = handler.MethodInfo.DeclaringType.FullName + "." + handler.MethodInfo.Name; string[] convertedArguments; if (arguments == null) @@ -95,15 +107,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } } + public static void ExecutingImplicitHandlerMethod(this ILogger logger, PageContext context) + { + if (logger.IsEnabled(LogLevel.Information)) + { + var validationState = context.ModelState.ValidationState; + + _implicitHandlerMethodExecuting(logger, validationState, null); + } + } + public static void ExecutedHandlerMethod(this ILogger logger, PageContext context, HandlerMethodDescriptor handler, IActionResult result) { - if (logger.IsEnabled(LogLevel.Debug)) + if (logger.IsEnabled(LogLevel.Information)) { var handlerName = handler.MethodInfo.Name; _handlerMethodExecuted(logger, handlerName, Convert.ToString(result), null); } } + public static void ExecutedImplicitHandlerMethod(this ILogger logger, IActionResult result) + { + if (logger.IsEnabled(LogLevel.Information)) + { + _implicitHandlerMethodExecuted(logger, Convert.ToString(result), null); + } + } + public static void BeforeExecutingMethodOnFilter(this ILogger logger, string filterType, string methodName, IFilterMetadata filter) { _beforeExecutingMethodOnFilter(logger, filterType, methodName, filter.GetType().ToString(), null); From 4e97bfe957c343991de0e93df2a579cd4b226503 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 6 May 2018 12:24:44 -0700 Subject: [PATCH 008/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 136 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 2b6bfb8aaf..ded91d14e1 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,81 +5,81 @@ 0.9.9 0.10.13 - 2.2.0-preview1-17042 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-17047 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 5.2.4 2.8.0-beta3 2.8.0-beta3 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 1.7.0 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 2.2.0-preview1-26424-04 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 2.0.0 2.2.0-preview1-26424-04 - 2.2.0-preview1-34066 - 2.2.0-preview1-34066 + 2.2.0-preview1-34135 + 2.2.0-preview1-34135 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 335e579e06..a16d4b9ee4 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17042 -commithash:edf0705d014293c260de763543784330514db9a3 +version:2.2.0-preview1-17047 +commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c From f35deb71f935832276f36d61d597fb2c37357f85 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 7 May 2018 10:45:54 -0700 Subject: [PATCH 009/316] Prevent KeyNotFound when logging --- .../Internal/MvcCoreLoggerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 0cee994b44..36221ff1c9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -695,7 +695,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal stringBuilder.Append($"{routeKeys[i]} = \"{routeValues[i]}\", "); } } - if (action.RouteValues["page"] != null) + if (action.RouteValues.TryGetValue("page", out var page) && page != null) { _pageExecuting(logger, stringBuilder.ToString(), action.DisplayName, null); } From dfd9b17f68408940da67a7e69e07087d4977f435 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 7 May 2018 11:08:41 -0700 Subject: [PATCH 010/316] More keynotfound avoidance --- .../Internal/MvcCoreLoggerExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 36221ff1c9..2050911090 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -784,7 +784,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Don't log if logging wasn't enabled at start of request as time will be wildly wrong. if (logger.IsEnabled(LogLevel.Information)) { - if (action.RouteValues["page"] != null) + if (action.RouteValues.TryGetValue("page", out var page) && page != null) { _pageExecuted(logger, action.DisplayName, timeSpan.TotalMilliseconds, null); } From 9007f5422dec1b791fe931bc69b7dc971d836979 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 3 May 2018 14:43:31 -0700 Subject: [PATCH 011/316] Bump Roslyn dependency to 2.8.0 (cherry picked from commit a8e1a1fd40a34fc80cd805a38effed28a81adb9d) --- build/dependencies.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index ded91d14e1..92dd9d1ee4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -43,8 +43,8 @@ 2.2.0-preview1-34135 2.2.0-preview1-34135 5.2.4 - 2.8.0-beta3 - 2.8.0-beta3 + 2.8.0 + 2.8.0 2.2.0-preview1-34135 1.7.0 2.2.0-preview1-34135 From f52c9c0f97fac7d355becd11bfa12413f5505cea Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 4 May 2018 08:45:48 -0700 Subject: [PATCH 012/316] Update analyzer tests to use Microsoft.AspNetCore.Analyzer.Testing --- build/dependencies.props | 141 ++++++------ korebuild-lock.txt | 4 +- .../ActionsMustNotBeAsyncVoidFacts.cs | 47 ++-- ...equireExplicitModelValidationCheckFacts.cs | 92 ++++---- .../ApiActionsAreAttributeRoutedFacts.cs | 26 ++- ...ApiActionsShouldUseActionResultOfTFacts.cs | 36 +-- .../Infrastructure/AnalyzerTestBase.cs | 8 +- ...ore.Mvc.Analyzers.Experimental.Test.csproj | 4 +- .../AvoidHtmlPartialAnalyzerTest.cs | 213 +++++------------- .../Infrastructure/Assert.cs | 194 ---------------- .../Infrastructure/DiagnosticResult.cs | 75 ------ .../MvcDiagnosticAnalyzerRunner.cs | 26 +++ .../Infrastructure/MvcTestSource.cs | 34 +++ ...osoft.AspNetCore.Mvc.Analyzers.Test.csproj | 1 + ...gnosticsAreReturned_ForUseOfHtmlPartial.cs | 0 ...Returned_ForUseOfHtmlPartial_InSections.cs | 0 ...eOfHtmlPartial_WithAdditionalParameters.cs | 0 ...osticsAreReturned_ForUseOfRenderPartial.cs | 0 ...turned_ForUseOfRenderPartial_InSections.cs | 0 ...fRenderPartial_WithAdditionalParameters.cs | 0 ...sticsAreReturned_ForNonUseOfHtmlPartial.cs | 0 ...icsAreReturned_ForUseOfHtmlPartialAsync.cs | 0 ...sAreReturned_ForUseOfRenderPartialAsync.cs | 0 23 files changed, 298 insertions(+), 603 deletions(-) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test}/Infrastructure/AnalyzerTestBase.cs (95%) delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/Assert.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/DiagnosticResult.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfRenderPartial.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/{ => AvoidHtmlPartialAnalyzerTest}/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs (100%) diff --git a/build/dependencies.props b/build/dependencies.props index 92dd9d1ee4..240fdc62c6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,81 +5,82 @@ 0.9.9 0.10.13 - 2.2.0-preview1-17047 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-17048 + 2.2.0-a-preview1-a2-16496 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 5.2.4 - 2.8.0 - 2.8.0 - 2.2.0-preview1-34135 + 2.8.0-beta3 + 2.8.0-beta3 + 2.2.0-preview1-34136 1.7.0 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 2.2.0-preview1-26424-04 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 2.0.0 2.2.0-preview1-26424-04 - 2.2.0-preview1-34135 - 2.2.0-preview1-34135 + 2.2.0-preview1-34136 + 2.2.0-preview1-34136 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index a16d4b9ee4..2573a03995 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17047 -commithash:e1957b52ddc8b62bd39c5c400322fccb5364624c +version:2.2.0-preview1-17048 +commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs index 1d536be95a..c7432399f1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs @@ -1,7 +1,9 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; @@ -12,6 +14,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ActionsMustNotBeAsyncVoidFacts : AnalyzerTestBase { + private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid; + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ActionsMustNotBeAsyncVoidAnalyzer(); @@ -57,13 +61,7 @@ public class UserViewModel public async Task DiagnosticsAreReturned_WhenMethodIsAControllerAction() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7003", - Message = "Controller actions must not have async void signature.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 7, 18) } - }; + var location = new DiagnosticLocation("Test.cs", 7, 18); var test = @" using Microsoft.AspNetCore.Mvc; @@ -92,7 +90,7 @@ public class HomeController : Controller // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(location, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } @@ -101,13 +99,7 @@ public class HomeController : Controller public async Task DiagnosticsAreReturned_WhenActionMethodIsExpressionBodied() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7003", - Message = "Controller actions must not have async void signature.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 7, 18) } - }; + var location = new DiagnosticLocation("Test.cs", 7, 18); var test = @" using Microsoft.AspNetCore.Mvc; @@ -130,7 +122,7 @@ public class HomeController : Controller // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(location, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } @@ -139,13 +131,7 @@ public class HomeController : Controller public async Task CodeFix_ProducesFullyQualifiedNamespaces() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7003", - Message = "Controller actions must not have async void signature.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 6, 18) } - }; + var location = new DiagnosticLocation("Test.cs", 6, 18); var test = @" using Microsoft.AspNetCore.Mvc; @@ -166,9 +152,22 @@ public class HomeController : Controller // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(location, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } + + private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) + { + // Assert + Assert.Collection( + actualDiagnostics, + diagnostic => + { + Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); + Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs index d74c584349..f2954b3047 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; @@ -12,6 +13,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsDoNotRequireExplicitModelValidationCheckFacts : AnalyzerTestBase { + private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter; + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer(); @@ -199,48 +202,11 @@ public class PetController : ControllerBase return VerifyAsync(test); } - private async Task VerifyAsync(string test) - { - // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7001", - Message = "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 9, 9) } - }; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - return Ok(); - } -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - [Fact] public async Task DiagnosticsAndCodeFixes_WhenModelStateIsInElseIf() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7001", - Message = "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 13, 9) } - }; + var expectedLocation = new DiagnosticLocation("Test.cs", 13, 9); var test = @" @@ -284,7 +250,7 @@ public class PetController : ControllerBase // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(expectedLocation, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } @@ -293,13 +259,7 @@ public class PetController : ControllerBase public async Task DiagnosticsAndCodeFixes_WhenModelStateIsInNestedBlock() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7001", - Message = "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 15, 13) } - }; + var expectedLocation = new DiagnosticLocation("Test.cs", 15, 13); var test = @" @@ -353,9 +313,47 @@ public class PetController : ControllerBase // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(expectedLocation, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } + + private async Task VerifyAsync(string test) + { + // Arrange + var expectedLocation = new DiagnosticLocation("Test.cs", 9, 9); + var expectedFix = +@" +using Microsoft.AspNetCore.Mvc; + +[ApiController] +public class PetController : ControllerBase +{ + public IActionResult GetPetId() + { + return Ok(); + } +}"; + var project = CreateProject(test); + + // Act & Assert + var actualDiagnostics = await GetDiagnosticAsync(project); + AssertDiagnostic(expectedLocation, actualDiagnostics); + var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); + Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); + } + + private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) + { + // Assert + Assert.Collection( + actualDiagnostics, + diagnostic => + { + Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); + Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs index c9225692aa..9042db0c0d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; @@ -12,6 +13,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsAreAttributeRoutedFacts : AnalyzerTestBase { + private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted; + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsAreAttributeRoutedAnalyzer(); @@ -128,13 +131,7 @@ public class PetController : Controller public async Task DiagnosticsAndCodeFixes_WhenApiControllerActionDoesNotHaveAttribute() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7000", - Message = "Actions on types annotated with ApiControllerAttribute must be attribute routed.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 8, 16) } - }; + var expectedLocation = new DiagnosticLocation("Test.cs", 8, 16); var test = @" using Microsoft.AspNetCore.Mvc; @@ -160,7 +157,7 @@ public class PetController : Controller // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(expectedLocation, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } @@ -290,5 +287,18 @@ public class PetController var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics, codeFixIndex: 3); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } + + private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) + { + // Assert + Assert.Collection( + actualDiagnostics, + diagnostic => + { + Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); + Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs index 5dc16ac2fe..ef939ffd3c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; @@ -12,6 +13,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsShouldUseActionResultOfTFacts : AnalyzerTestBase { + private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf; + protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsShouldUseActionResultOfTAnalyzer(); @@ -156,13 +159,7 @@ public class PetController: ControllerBase public async Task DiagnosticsAreReturned_WhenActionsReturnIActionResult() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7002", - Message = "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 9, 12) } - }; + var expectedLocation = new DiagnosticLocation("Test.cs", 9, 12); var test = @" using Microsoft.AspNetCore.Mvc; @@ -195,7 +192,7 @@ public class PetController: ControllerBase // Act var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(expectedLocation, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); @@ -205,13 +202,7 @@ public class PetController: ControllerBase public async Task DiagnosticsAreReturned_WhenActionReturnsAsyncIActionResult() { // Arrange - var expectedDiagnostic = new DiagnosticResult - { - Id = "MVC7002", - Message = "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - Severity = DiagnosticSeverity.Warning, - Locations = new[] { new DiagnosticResultLocation("Test.cs", 8, 18) } - }; + var expectedLocation = new DiagnosticLocation("Test.cs", 8, 18); var test = @" @@ -248,10 +239,23 @@ public class Pet {}"; // Act & Assert var actualDiagnostics = await GetDiagnosticAsync(project); - Assert.DiagnosticsEqual(new[] { expectedDiagnostic }, actualDiagnostics); + AssertDiagnostic(expectedLocation, actualDiagnostics); var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); } + + private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) + { + // Assert + Assert.Collection( + actualDiagnostics, + diagnostic => + { + Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); + Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/AnalyzerTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs similarity index 95% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/AnalyzerTestBase.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs index 73f2cae3f6..1351b24c94 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/AnalyzerTestBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Testing; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeActions; @@ -17,6 +18,7 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Text; using Microsoft.Extensions.DependencyModel; +using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure { @@ -30,9 +32,9 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure protected virtual CodeFixProvider CodeFixProvider { get; } - public IDictionary MarkerLocations { get; } = new Dictionary(); + public IDictionary MarkerLocations { get; } = new Dictionary(); - public DiagnosticResultLocation? DefaultMarkerLocation { get; private set; } + public DiagnosticLocation DefaultMarkerLocation { get; private set; } protected Project CreateProjectFromFile([CallerMemberName] string fileName = "") { @@ -57,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure { var markerEndIndex = line.IndexOf(MarkerEnd, markerStartIndex, StringComparison.Ordinal); var markerName = line.Substring(markerStartIndex + 2, markerEndIndex - markerStartIndex - 2); - var resultLocation = new DiagnosticResultLocation(i + 1, markerStartIndex + 1); ; + var resultLocation = new DiagnosticLocation(i + 1, markerStartIndex + 1); ; if (DefaultMarkerLocation == null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj index b5d9571c75..86f4228e01 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) @@ -7,13 +7,13 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs index 1dc47a4110..b6de06c5fa 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs @@ -1,68 +1,77 @@ // 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.Runtime.CompilerServices; using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers { - public class AvoidHtmlPartialAnalyzerTest : AnalyzerTestBase + public class AvoidHtmlPartialAnalyzerTest { private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC1000_HtmlHelperPartialShouldBeAvoided; - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new AvoidHtmlPartialAnalyzer(); + private MvcDiagnosticAnalyzerRunner Executor { get; } = new MvcDiagnosticAnalyzerRunner(new AvoidHtmlPartialAnalyzer()); [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var project = CreateProject(source: string.Empty); + public Task NoDiagnosticsAreReturned_FoEmptyScenarios() + => VerifyNoDiagnosticsAreReturned(source: string.Empty); + [Fact] + public Task NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial() + => VerifyNoDiagnosticsAreReturned(ReadTestSource().Source); + + [Fact] + public Task NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync() + => VerifyNoDiagnosticsAreReturned(ReadTestSource().Source); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfHtmlPartial() + => VerifyDefault(ReadTestSource()); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters() + => VerifyDefault(ReadTestSource()); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections() + => VerifyDefault(ReadTestSource()); + + [Fact] + public Task NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync() + => VerifyNoDiagnosticsAreReturned(ReadTestSource().Source); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfRenderPartial() + => VerifyDefault(ReadTestSource()); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters() + => VerifyDefault(ReadTestSource()); + + [Fact] + public Task DiagnosticsAreReturned_ForUseOfRenderPartial_InSections() + => VerifyDefault(ReadTestSource()); + + private async Task VerifyNoDiagnosticsAreReturned(string source) + { // Act - var result = await GetDiagnosticAsync(project); + var result = await Executor.GetDiagnosticsAsync(source); // Assert Assert.Empty(result); } - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial() + private async Task VerifyDefault(TestSource testSource) { // Arrange - var project = CreateProjectFromFile(); + var expectedLocation = testSource.DefaultMarkerLocation; // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync() - { - // Arrange - var project = CreateProjectFromFile(); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfHtmlPartial() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); + var result = await Executor.GetDiagnosticsAsync(testSource.Source); // Assert Assert.Collection( @@ -72,131 +81,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); }); } - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Collection( - result, - diagnostic => - { - - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Collection( - result, - diagnostic => - { - - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync() - { - // Arrange - var project = CreateProjectFromFile(); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfRenderPartial() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Collection( - result, - diagnostic => - { - - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Collection( - result, - diagnostic => - { - - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - - [Fact] - public async Task DiagnosticsAreReturned_ForUseOfRenderPartial_InSections() - { - // Arrange - var project = CreateProjectFromFile(); - var expectedLocation = DefaultMarkerLocation.Value; - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Collection( - result, - diagnostic => - { - - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - Assert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } + private static TestSource ReadTestSource([CallerMemberName] string testMethod = "") => + MvcTestSource.Read(nameof(AvoidHtmlPartialAnalyzerTest), testMethod); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/Assert.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/Assert.cs deleted file mode 100644 index ea57f2f5b8..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/Assert.cs +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Formatting; -using Microsoft.CodeAnalysis.Simplification; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - internal class Assert : Xunit.Assert - { - public static void DiagnosticsEqual(IEnumerable expected, IEnumerable actual) - { - var expectedCount = expected.Count(); - var actualCount = actual.Count(); - - if (expectedCount != actualCount) - { - throw new DiagnosticsAssertException( - expected, - actual, - $"Mismatch between number of diagnostics returned, expected \"{expectedCount}\" actual \"{actualCount}."); - } - - foreach (var (expectedItem, actualItem) in expected.Zip(actual, (a, b) => (a, b))) - { - if (expectedItem.Line == -1 && expectedItem.Column == -1) - { - if (actualItem.Location != Location.None) - { - throw new DiagnosticAssertException( - expectedItem, - actualItem, - $"Expected: A project diagnostic with no location. Actual {actualItem.Location}."); - } - } - else - { - VerifyLocation(expectedItem, actualItem); - } - - if (actualItem.Id != expectedItem.Id) - { - throw new DiagnosticAssertException( - expectedItem, - actualItem, - $"Expected: Expected id: {expectedItem.Id}. Actual id: {actualItem.Id}."); - } - - if (actualItem.Severity != expectedItem.Severity) - { - throw new DiagnosticAssertException( - expectedItem, - actualItem, - $"Expected: Expected severity: {expectedItem.Severity}. Actual severity: {actualItem.Severity}."); - } - - if (actualItem.GetMessage() != expectedItem.Message) - { - throw new DiagnosticAssertException( - expectedItem, - actualItem, - $"Expected: Expected message: {expectedItem.Message}. Actual message: {actualItem.GetMessage()}."); - } - } - } - - private static void VerifyLocation(DiagnosticResult expected, Diagnostic actual) - { - if (expected.Locations.Length == 0) - { - return; - } - - var expectedLocation = expected.Locations[0]; - Assert.DiagnosticLocation(expectedLocation, actual.Location); - - } - - public static void DiagnosticLocation(DiagnosticResultLocation expected, Location actual) - { - var actualSpan = actual.GetLineSpan(); - var actualLinePosition = actualSpan.StartLinePosition; - - // Only check line position if there is an actual line in the real diagnostic - if (actualLinePosition.Line > 0) - { - if (actualLinePosition.Line + 1 != expected.Line) - { - throw new DiagnosticLocationAssertException( - expected, - actual, - $"Expected diagnostic to be on line \"{expected.Line}\" was actually on line \"{actualLinePosition.Line + 1}\""); - } - } - - // Only check column position if there is an actual column position in the real diagnostic - if (actualLinePosition.Character > 0) - { - if (actualLinePosition.Character + 1 != expected.Column) - { - throw new DiagnosticLocationAssertException( - expected, - actual, - $"Expected diagnostic to start at column \"{expected.Column}\" was actually on line \"{actualLinePosition.Character + 1}\""); - } - } - } - - private static string FormatDiagnostics(IEnumerable diagnostics) - { - return string.Join(Environment.NewLine, diagnostics.Select(FormatDiagnostic)); - } - - private static string FormatDiagnostic(Diagnostic diagnostic) - { - var builder = new StringBuilder(); - builder.AppendLine(diagnostic.ToString()); - - var location = diagnostic.Location; - if (location == Location.None) - { - builder.Append($"Location unknown: ({diagnostic.Id})"); - } - else - { - True(location.IsInSource, - $"Test base does not currently handle diagnostics in metadata locations. Diagnostic in metadata: {diagnostic}"); - - var linePosition = location.GetLineSpan().StartLinePosition; - builder.Append($"({(linePosition.Line + 1)}, {(linePosition.Character + 1)}, {diagnostic.Id})"); - } - - return builder.ToString(); - } - - private static async Task GetStringFromDocumentAsync(Document document) - { - var simplifiedDoc = await Simplifier.ReduceAsync(document, Simplifier.Annotation); - var root = await simplifiedDoc.GetSyntaxRootAsync(); - root = Formatter.Format(root, Formatter.Annotation, simplifiedDoc.Project.Solution.Workspace); - return root.GetText().ToString(); - } - - private class DiagnosticsAssertException : Xunit.Sdk.EqualException - { - public DiagnosticsAssertException( - IEnumerable expected, - IEnumerable actual, - string message) - : base(expected, actual) - { - Message = message + Environment.NewLine + FormatDiagnostics(actual); - } - - public override string Message { get; } - } - - private class DiagnosticAssertException : Xunit.Sdk.EqualException - { - public DiagnosticAssertException( - DiagnosticResult expected, - Diagnostic actual, - string message) - : base(expected, actual) - { - Message = message + Environment.NewLine + FormatDiagnostic(actual); - } - - public override string Message { get; } - } - - private class DiagnosticLocationAssertException : Xunit.Sdk.EqualException - { - public DiagnosticLocationAssertException( - DiagnosticResultLocation expected, - Location actual, - string message) - : base(expected, actual) - { - Message = message; - } - - public override string Message { get; } - } - - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/DiagnosticResult.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/DiagnosticResult.cs deleted file mode 100644 index c1c32bd025..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/DiagnosticResult.cs +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure -{ - /// - /// Location where the diagnostic appears, as determined by path, line number, and column number. - /// - public struct DiagnosticResultLocation - { - public DiagnosticResultLocation(int line, int column) - : this("Test.cs", line, column) - { - } - - public DiagnosticResultLocation(string path, int line, int column) - { - if (line < -1) - { - throw new ArgumentOutOfRangeException(nameof(line), "line must be >= -1"); - } - - if (column < -1) - { - throw new ArgumentOutOfRangeException(nameof(column), "column must be >= -1"); - } - - Path = path; - Line = line; - Column = column; - } - - public string Path { get; } - public int Line { get; } - public int Column { get; } - } - - /// - /// Struct that stores information about a Diagnostic appearing in a source - /// - public struct DiagnosticResult - { - private DiagnosticResultLocation[] _locations; - - public DiagnosticResultLocation[] Locations - { - get - { - if (_locations == null) - { - _locations = new DiagnosticResultLocation[] { }; - } - - return _locations; - } - - set => _locations = value; - } - - public DiagnosticSeverity Severity { get; set; } - - public string Id { get; set; } - - public string Message { get; set; } - - public string Path => Locations.Length > 0 ? Locations[0].Path : ""; - - public int Line => Locations.Length > 0 ? Locations[0].Line : -1; - - public int Column => Locations.Length > 0 ? Locations[0].Column : -1; - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs new file mode 100644 index 0000000000..d3a8d94b73 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.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; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure +{ + public class MvcDiagnosticAnalyzerRunner : DiagnosticAnalyzerRunner + { + public MvcDiagnosticAnalyzerRunner(DiagnosticAnalyzer analyzer) + { + Analyzer = analyzer; + } + + public DiagnosticAnalyzer Analyzer { get; } + + public Task GetDiagnosticsAsync(string source) + { + return GetDiagnosticsAsync(sources: new[] { source }, Analyzer, Array.Empty()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs new file mode 100644 index 0000000000..19c19140d1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs @@ -0,0 +1,34 @@ +// 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.Analyzer.Testing; +using Microsoft.AspNetCore.Testing; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure +{ + public static class MvcTestSource + { + private static readonly string ProjectDirectory = GetProjectDirectory(); + + public static TestSource Read(string testClassName, string testMethod) + { + var filePath = Path.Combine(ProjectDirectory, "TestFiles", testClassName, testMethod + ".cs"); + if (!File.Exists(filePath)) + { + throw new FileNotFoundException($"TestFile {testMethod} could not be found at {filePath}.", filePath); + } + + var fileContent = File.ReadAllText(filePath); + return TestSource.Read(fileContent); + } + + private static string GetProjectDirectory() + { + var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Mvc"); + var assemblyName = typeof(MvcTestSource).Assembly.GetName().Name; + var projectDirectory = Path.Combine(solutionDirectory, "test", assemblyName); + return projectDirectory; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj index 53db6da506..92de24ec50 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj @@ -13,6 +13,7 @@ + diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs rename to test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs From 26454fb1da36191ee43b59b956f6e9f2d945a3a7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 5 Feb 2018 17:44:20 -0800 Subject: [PATCH 013/316] Add ViewComponentResult helper methods to Page \ PageModel Fixes #7051 --- .../PageBase.cs | 75 +++++++++++++++- .../PageModel.cs | 81 ++++++++++++++++-- .../PageModelTest.cs | 85 ++++++++++++++++--- .../PageTest.cs | 70 +++++++++++++++ 4 files changed, 290 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs index 12b88777e8..b23063eeb0 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs @@ -149,7 +149,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// Creates a that produces a response. /// /// The created for the response. - [NonAction] public virtual BadRequestResult BadRequest() => new BadRequestResult(); @@ -158,7 +157,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// An error object to be returned to the client. /// The created for the response. - [NonAction] public virtual BadRequestObjectResult BadRequest(object error) => new BadRequestObjectResult(error); @@ -167,7 +165,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// The containing errors to be returned to the client. /// The created for the response. - [NonAction] public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) { if (modelState == null) @@ -1217,6 +1214,78 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public virtual UnauthorizedResult Unauthorized() => new UnauthorizedResult(); + #region ViewComponentResult + /// + /// Creates a by specifying the name of a view component to render. + /// + /// + /// The view component name. Can be a view component + /// or + /// . + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(string componentName) + { + return ViewComponent(componentName, arguments: null); + } + + /// + /// Creates a by specifying the of a view component to + /// render. + /// + /// The view component . + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(Type componentType) + { + return ViewComponent(componentType, arguments: null); + } + + /// + /// Creates a by specifying the name of a view component to render. + /// + /// + /// The view component name. Can be a view component + /// or + /// . + /// + /// An with properties representing arguments to be passed to the invoked view component + /// method. Alternatively, an instance + /// containing the invocation arguments. + /// + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(string componentName, object arguments) + { + return new ViewComponentResult + { + ViewComponentName = componentName, + Arguments = arguments, + ViewData = ViewContext.ViewData, + TempData = TempData + }; + } + + /// + /// Creates a by specifying the of a view component to + /// render. + /// + /// The view component . + /// + /// An with properties representing arguments to be passed to the invoked view component + /// method. Alternatively, an instance + /// containing the invocation arguments. + /// + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(Type componentType, object arguments) + { + return new ViewComponentResult + { + ViewComponentType = componentType, + Arguments = arguments, + ViewData = ViewContext.ViewData, + TempData = TempData + }; + } + #endregion + /// /// Updates the specified instance using values from the 's current /// . diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs index ce5a76af27..526d0fad72 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages } /// - /// Gets or sets used by . + /// Gets the . /// public ViewDataDictionary ViewData => PageContext?.ViewData; @@ -524,7 +524,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// Creates a that produces a response. /// /// The created for the response. - [NonAction] public virtual BadRequestResult BadRequest() => new BadRequestResult(); @@ -533,7 +532,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// An error object to be returned to the client. /// The created for the response. - [NonAction] public virtual BadRequestObjectResult BadRequest(object error) => new BadRequestObjectResult(error); @@ -542,7 +540,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// The containing errors to be returned to the client. /// The created for the response. - [NonAction] public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) { if (modelState == null) @@ -1617,6 +1614,78 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public virtual UnauthorizedResult Unauthorized() => new UnauthorizedResult(); + #region ViewComponentResult + /// + /// Creates a by specifying the name of a view component to render. + /// + /// + /// The view component name. Can be a view component + /// or + /// . + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(string componentName) + { + return ViewComponent(componentName, arguments: null); + } + + /// + /// Creates a by specifying the of a view component to + /// render. + /// + /// The view component . + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(Type componentType) + { + return ViewComponent(componentType, arguments: null); + } + + /// + /// Creates a by specifying the name of a view component to render. + /// + /// + /// The view component name. Can be a view component + /// or + /// . + /// + /// An with properties representing arguments to be passed to the invoked view component + /// method. Alternatively, an instance + /// containing the invocation arguments. + /// + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(string componentName, object arguments) + { + return new ViewComponentResult + { + ViewComponentName = componentName, + Arguments = arguments, + ViewData = ViewData, + TempData = TempData + }; + } + + /// + /// Creates a by specifying the of a view component to + /// render. + /// + /// The view component . + /// + /// An with properties representing arguments to be passed to the invoked view component + /// method. Alternatively, an instance + /// containing the invocation arguments. + /// + /// The created object for the response. + public virtual ViewComponentResult ViewComponent(Type componentType, object arguments) + { + return new ViewComponentResult + { + ViewComponentType = componentType, + Arguments = arguments, + ViewData = ViewData, + TempData = TempData + }; + } + #endregion + /// /// Validates the specified instance. /// @@ -1657,7 +1726,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages return ModelState.IsValid; } -#region IAsyncPageFilter \ IPageFilter + #region IAsyncPageFilter \ IPageFilter /// /// Called after a handler method has been selected, but before model binding occurs. /// @@ -1724,6 +1793,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages OnPageHandlerExecuted(await next()); } } -#endregion + #endregion } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index 6d0913d394..b73abd57b9 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -329,80 +329,80 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages [InlineData("")] [InlineData(null)] [InlineData("SampleController")] - public void RedirectToAction_WithParameterActionAndControllerName_SetsEqualNames(string pageModelName) + public void RedirectToAction_WithParameterActionAndControllerName_SetsEqualNames(string controllerName) { // Arrange var pageModel = new TestPageModel(); // Act - var resultTemporary = pageModel.RedirectToAction("SampleAction", pageModelName); + var resultTemporary = pageModel.RedirectToAction("SampleAction", controllerName); // Assert Assert.IsType(resultTemporary); Assert.False(resultTemporary.PreserveMethod); Assert.False(resultTemporary.Permanent); Assert.Equal("SampleAction", resultTemporary.ActionName); - Assert.Equal(pageModelName, resultTemporary.ControllerName); + Assert.Equal(controllerName, resultTemporary.ControllerName); } [Theory] [InlineData("")] [InlineData(null)] [InlineData("SampleController")] - public void RedirectToActionPreserveMethod_WithParameterActionAndControllerName_SetsEqualNames(string pageModelName) + public void RedirectToActionPreserveMethod_WithParameterActionAndControllerName_SetsEqualNames(string controllerName) { // Arrange var pageModel = new TestPageModel(); // Act - var resultTemporary = pageModel.RedirectToActionPreserveMethod(actionName: "SampleAction", controllerName: pageModelName); + var resultTemporary = pageModel.RedirectToActionPreserveMethod(actionName: "SampleAction", controllerName: controllerName); // Assert Assert.IsType(resultTemporary); Assert.True(resultTemporary.PreserveMethod); Assert.False(resultTemporary.Permanent); Assert.Equal("SampleAction", resultTemporary.ActionName); - Assert.Equal(pageModelName, resultTemporary.ControllerName); + Assert.Equal(controllerName, resultTemporary.ControllerName); } [Theory] [InlineData("")] [InlineData(null)] [InlineData("SampleController")] - public void RedirectToActionPermanent_WithParameterActionAndControllerName_SetsEqualNames(string pageModelName) + public void RedirectToActionPermanent_WithParameterActionAndControllerName_SetsEqualNames(string controllerName) { // Arrange var pageModel = new TestPageModel(); // Act - var resultPermanent = pageModel.RedirectToActionPermanent("SampleAction", pageModelName); + var resultPermanent = pageModel.RedirectToActionPermanent("SampleAction", controllerName); // Assert Assert.IsType(resultPermanent); Assert.False(resultPermanent.PreserveMethod); Assert.True(resultPermanent.Permanent); Assert.Equal("SampleAction", resultPermanent.ActionName); - Assert.Equal(pageModelName, resultPermanent.ControllerName); + Assert.Equal(controllerName, resultPermanent.ControllerName); } [Theory] [InlineData("")] [InlineData(null)] [InlineData("SampleController")] - public void RedirectToActionPermanentPreserveMethod_WithParameterActionAndControllerName_SetsEqualNames(string pageModelName) + public void RedirectToActionPermanentPreserveMethod_WithParameterActionAndControllerName_SetsEqualNames(string controllerName) { // Arrange var pageModel = new TestPageModel(); // Act - var resultPermanent = pageModel.RedirectToActionPermanentPreserveMethod(actionName: "SampleAction", controllerName: pageModelName); + var resultPermanent = pageModel.RedirectToActionPermanentPreserveMethod(actionName: "SampleAction", controllerName: controllerName); // Assert Assert.IsType(resultPermanent); Assert.True(resultPermanent.PreserveMethod); Assert.True(resultPermanent.Permanent); Assert.Equal("SampleAction", resultPermanent.ActionName); - Assert.Equal(pageModelName, resultPermanent.ControllerName); + Assert.Equal(controllerName, resultPermanent.ControllerName); } [Theory] @@ -1895,6 +1895,67 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages testPageModel.Verify(); } + [Fact] + public void ViewComponent_WithName() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPageModel + { + PageContext = new PageContext + { + ViewData = viewData, + }, + }; + + // Act + var result = pageModel.ViewComponent("TagCloud"); + + // Assert + Assert.NotNull(result); + Assert.Equal("TagCloud", result.ViewComponentName); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void ViewComponent_WithType() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPageModel + { + PageContext = new PageContext + { + ViewData = viewData, + }, + }; + + // Act + var result = pageModel.ViewComponent(typeof(Guid)); + + // Assert + Assert.NotNull(result); + Assert.Equal(typeof(Guid), result.ViewComponentType); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void ViewComponent_WithArguments() + { + // Arrange + var pageModel = new TestPageModel(); + var arguments = new { Arg1 = "Hi", Arg2 = "There" }; + + // Act + var result = pageModel.ViewComponent(typeof(Guid), arguments); + + // Assert + Assert.NotNull(result); + + Assert.Equal(typeof(Guid), result.ViewComponentType); + Assert.Same(arguments, result.Arguments); + } + private class ContentPageModel : PageModel { public IActionResult Content_WithNoEncoding() diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs index aaefccfcc9..f253c06d43 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs @@ -1698,6 +1698,76 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages Assert.Equal(statusCode, result.StatusCode); } + [Fact] + public void ViewComponent_WithName() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var page = new TestPage + { + ViewContext = new ViewContext + { + ViewData = viewData, + }, + }; + + // Act + var result = page.ViewComponent("TagCloud"); + + // Assert + Assert.NotNull(result); + Assert.Equal("TagCloud", result.ViewComponentName); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void ViewComponent_WithType() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var page = new TestPage + { + ViewContext = new ViewContext + { + ViewData = viewData, + }, + }; + + // Act + var result = page.ViewComponent(typeof(Guid)); + + // Assert + Assert.NotNull(result); + Assert.Equal(typeof(Guid), result.ViewComponentType); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void ViewComponent_WithArguments() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var page = new TestPage + { + ViewContext = new ViewContext + { + ViewData = viewData, + }, + }; + + var arguments = new { Arg1 = "Hi", Arg2 = "There" }; + + // Act + var result = page.ViewComponent(typeof(Guid), arguments); + + // Assert + Assert.NotNull(result); + + Assert.Equal(typeof(Guid), result.ViewComponentType); + Assert.Same(arguments, result.Arguments); + Assert.Same(viewData, result.ViewData); + } + public static IEnumerable RedirectTestData { get From bda5ea9df08203bd489f600f36d461bd8cdfb953 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 7 May 2018 17:18:20 -0700 Subject: [PATCH 014/316] Cleanup BindingInfo \ ModelMetadata coalescing in ModelBinderFactory Fixes #7583 --- .../ModelBinding/ModelBinderFactory.cs | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs index 216fc7c803..08e6883dd8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs @@ -275,15 +275,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public override IModelBinder CreateBinder(ModelMetadata metadata) { - return CreateBinder( - metadata, - new BindingInfo() - { - BinderModelName = metadata.BinderModelName, - BinderType = metadata.BinderType, - BindingSource = metadata.BindingSource, - PropertyFilterProvider = metadata.PropertyFilterProvider, - }); + var bindingInfo = new BindingInfo(); + bindingInfo.TryApplyBindingInfo(metadata); + + return CreateBinder(metadata, bindingInfo); } public override IModelBinder CreateBinder(ModelMetadata metadata, BindingInfo bindingInfo) From 9f7629b4481a34463ec064000d446fd5d6038c8f Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 7 May 2018 15:13:58 -0700 Subject: [PATCH 015/316] Upgrade to netcoreapp22 --- Directory.Build.targets | 5 ++++- build/dependencies.props | 3 ++- build/repo.props | 3 ++- samples/MvcSandbox/MvcSandbox.csproj | 4 ++-- test/Directory.Build.props | 4 ++-- .../Microsoft.AspNetCore.Mvc.FunctionalTests.csproj | 4 ++-- test/WebSites/Directory.Build.props | 4 ++-- 7 files changed, 16 insertions(+), 11 deletions(-) diff --git a/Directory.Build.targets b/Directory.Build.targets index eb03b2565f..73b97f2807 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,6 +1,9 @@ - + $(MicrosoftNETCoreApp21PackageVersion) + $(MicrosoftNETCoreApp22PackageVersion) $(NETStandardLibrary20PackageVersion) + + 99.9 diff --git a/build/dependencies.props b/build/dependencies.props index 240fdc62c6..b6b78abcbd 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -78,7 +78,8 @@ 2.2.0-preview1-34136 2.2.0-preview1-34136 2.0.0 - 2.2.0-preview1-26424-04 + 2.1.0-rc1 + 2.2.0-preview1-26502-01 2.2.0-preview1-34136 2.2.0-preview1-34136 15.6.1 diff --git a/build/repo.props b/build/repo.props index a1e8d612e5..56142440e8 100644 --- a/build/repo.props +++ b/build/repo.props @@ -1,4 +1,4 @@ - + @@ -18,5 +18,6 @@ + diff --git a/samples/MvcSandbox/MvcSandbox.csproj b/samples/MvcSandbox/MvcSandbox.csproj index 116e4a2f66..d9e021ff7a 100644 --- a/samples/MvcSandbox/MvcSandbox.csproj +++ b/samples/MvcSandbox/MvcSandbox.csproj @@ -1,7 +1,7 @@ - + - netcoreapp2.1 + netcoreapp2.2 $(TargetFrameworks);net461 diff --git a/test/Directory.Build.props b/test/Directory.Build.props index ece617ffe1..7ba9394b16 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -2,9 +2,9 @@ - netcoreapp2.1 + netcoreapp2.2 $(DeveloperBuildTestTfms) - netcoreapp2.1 + $(StandardTestTfms);net461 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 954ef92727..813e61ff85 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -1,9 +1,9 @@ - + $(StandardTestTfms) - + $(DefineConstants);GENERATE_BASELINES $(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES diff --git a/test/WebSites/Directory.Build.props b/test/WebSites/Directory.Build.props index ed8c546e2a..2ff6b1fb54 100644 --- a/test/WebSites/Directory.Build.props +++ b/test/WebSites/Directory.Build.props @@ -3,9 +3,9 @@ - netcoreapp2.1 + netcoreapp2.2 $(DeveloperBuildTestWebsiteTfms) - netcoreapp2.1 + netcoreapp2.2 $(StandardTestWebsiteTfms);net461 From ec2d5c7aa44db3384a50a5eca0098072ff8f47ff Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Mon, 7 May 2018 22:26:05 -0700 Subject: [PATCH 016/316] Run functional tests with a `TestLoggerFactory` - #7744 --- .../Infrastructure/MvcTestFixture.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs index 7e32759610..a7d4a643de 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs @@ -7,6 +7,8 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Testing; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { @@ -19,8 +21,15 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests .UseRequestCulture("en-GB", "en-US") .UseEnvironment("Production") .ConfigureServices( - services => services.Configure( - options => options.CompatibilityVersion = CompatibilityVersion.Version_2_1)); + services => + { + var testSink = new TestSink(); + var loggerFactory = new TestLoggerFactory(testSink, enabled: true); + services.AddSingleton(loggerFactory); + + services.Configure( + options => options.CompatibilityVersion = CompatibilityVersion.Version_2_1); + }); } protected override TestServer CreateServer(IWebHostBuilder builder) From 504da3c565a9a159365b4564dba5f2cd7d059e7f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 3 May 2018 15:44:53 -0700 Subject: [PATCH 017/316] Cleanup CachedExpressionCompiler --- .../Internal/CachedExpressionCompiler.cs | 157 ------------------ .../Internal/ExpressionHelper.cs | 3 +- .../Internal/ExpressionMetadataProvider.cs | 8 +- .../ViewFeatures/CachedExpressionCompiler.cs | 152 +++++++++++++++++ 4 files changed, 158 insertions(+), 162 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CachedExpressionCompiler.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CachedExpressionCompiler.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CachedExpressionCompiler.cs deleted file mode 100644 index aa35a7592f..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CachedExpressionCompiler.cs +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; -using System.Reflection; - -namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal -{ - public static class CachedExpressionCompiler - { - // This is the entry point to the cached expression compilation system. The system - // will try to turn the expression into an actual delegate as quickly as possible, - // relying on cache lookups and other techniques to save time if appropriate. - // If the provided expression is particularly obscure and the system doesn't know - // how to handle it, we'll just compile the expression as normal. - public static Func Process( - Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - return Compiler.Compile(expression); - } - - private static class Compiler - { - private static Func _identityFunc; - - private static readonly ConcurrentDictionary> _simpleMemberAccessCache = - new ConcurrentDictionary>(); - - private static readonly ConcurrentDictionary> _constMemberAccessCache = - new ConcurrentDictionary>(); - - public static Func Compile(Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - return CompileFromIdentityFunc(expression) - ?? CompileFromConstLookup(expression) - ?? CompileFromMemberAccess(expression) - ?? CompileSlow(expression); - } - - private static Func CompileFromConstLookup( - Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - var constantExpression = expression.Body as ConstantExpression; - if (constantExpression != null) - { - // model => {const} - - var constantValue = (TResult)constantExpression.Value; - return _ => constantValue; - } - - return null; - } - - private static Func CompileFromIdentityFunc( - Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - if (expression.Body == expression.Parameters[0]) - { - // model => model - - // Don't need to lock, as all identity funcs are identical. - if (_identityFunc == null) - { - _identityFunc = expression.Compile(); - } - - return _identityFunc; - } - - return null; - } - - private static Func CompileFromMemberAccess( - Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - // Performance tests show that on the x64 platform, special-casing static member and - // captured local variable accesses is faster than letting the fingerprinting system - // handle them. On the x86 platform, the fingerprinting system is faster, but only - // by around one microsecond, so it's not worth it to complicate the logic here with - // an architecture check. - - var memberExpression = expression.Body as MemberExpression; - if (memberExpression != null) - { - if (memberExpression.Expression == expression.Parameters[0] || memberExpression.Expression == null) - { - // model => model.Member or model => StaticMember - return _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, _ => expression.Compile()); - } - - var constantExpression = memberExpression.Expression as ConstantExpression; - if (constantExpression != null) - { - // model => {const}.Member (captured local variable) - var compiledExpression = _constMemberAccessCache.GetOrAdd(memberExpression.Member, _ => - { - // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member - var parameterExpression = Expression.Parameter(typeof(object), "capturedLocal"); - var castExpression = - Expression.Convert(parameterExpression, memberExpression.Member.DeclaringType); - var replacementMemberExpression = memberExpression.Update(castExpression); - var replacementExpression = Expression.Lambda>( - replacementMemberExpression, - parameterExpression); - - return replacementExpression.Compile(); - }); - - var capturedLocal = constantExpression.Value; - return _ => compiledExpression(capturedLocal); - } - } - - return null; - } - - private static Func CompileSlow(Expression> expression) - { - if (expression == null) - { - throw new ArgumentNullException(nameof(expression)); - } - - // fallback compilation system - just compile the expression directly - return expression.Compile(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs index b2c47f3892..90df635a9c 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs @@ -31,9 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal throw new ArgumentNullException(nameof(expression)); } - string expressionText; if (expressionTextCache != null && - expressionTextCache.Entries.TryGetValue(expression, out expressionText)) + expressionTextCache.Entries.TryGetValue(expression, out var expressionText)) { return expressionText; } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs index 38d89f80bc..56d7774ad7 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs @@ -80,17 +80,19 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal throw new InvalidOperationException(Resources.TemplateHelpers_TemplateLimitations); } - Func modelAccessor = (container) => + object modelAccessor(object container) { + var compiledExpression = CachedExpressionCompiler.Process(expression); + Debug.Assert(compiledExpression != null); try { - return CachedExpressionCompiler.Process(expression)((TModel)container); + return compiledExpression((TModel)container); } catch (NullReferenceException) { return null; } - }; + } ModelMetadata metadata = null; if (containerType != null && propertyName != null) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs new file mode 100644 index 0000000000..f41448d8f3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs @@ -0,0 +1,152 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + internal static class CachedExpressionCompiler + { + // This is the entry point to the cached expression compilation system. The system + // will try to turn the expression into an actual delegate as quickly as possible, + // relying on cache lookups and other techniques to save time if appropriate. + // If the provided expression is particularly obscure and the system doesn't know + // how to handle it, we'll just compile the expression as normal. + public static Func Process( + Expression> expression) + { + if (expression == null) + { + throw new ArgumentNullException(nameof(expression)); + } + + return Compiler.Compile(expression); + } + + private static class Compiler + { + private static Func _identityFunc; + + private static readonly ConcurrentDictionary> _simpleMemberAccessCache = + new ConcurrentDictionary>(); + + private static readonly ConcurrentDictionary> _constMemberAccessCache = + new ConcurrentDictionary>(); + + public static Func Compile(Expression> expression) + { + Debug.Assert(expression != null); + + switch (expression.Body) + { + // model => model + case var body when body == expression.Parameters[0]: + return CompileFromIdentityFunc(expression); + + // model => (object){const} + case ConstantExpression constantExpression: + return CompileFromConstLookup(constantExpression); + + // model => CapturedConstant + case MemberExpression memberExpression when memberExpression.Expression is ConstantExpression constantExpression: + return CompileCapturedConstant(memberExpression, constantExpression); + + // model => StaticMember + case MemberExpression memberExpression when memberExpression.Expression == null: + return CompileFromStaticMemberAccess(expression, memberExpression); + + // model => model.Member + case MemberExpression memberExpression when memberExpression.Expression == expression.Parameters[0]: + return CompileFromMemberAccess(expression, memberExpression); + + default: + return CompileSlow(expression); + } + } + + private static Func CompileFromConstLookup( + ConstantExpression constantExpression) + { + // model => {const} + var constantValue = (TResult)constantExpression.Value; + return _ => constantValue; + } + + private static Func CompileFromIdentityFunc( + Expression> expression) + { + // model => model + + // Don't need to lock, as all identity funcs are identical. + if (_identityFunc == null) + { + _identityFunc = expression.Compile(); + } + + return _identityFunc; + } + + private static Func CompileFromMemberAccess( + Expression> expression, + MemberExpression memberExpression) + { + // model => model.Member + if (_simpleMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) + { + return result; + } + + result = expression.Compile(); + result = _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, result); + return result; + } + + private static Func CompileFromStaticMemberAccess( + Expression> expression, + MemberExpression memberExpression) + { + // model => model.StaticMember + if (_simpleMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) + { + return result; + } + + result = expression.Compile(); + result = _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, result); + return result; + } + + private static Func CompileCapturedConstant(MemberExpression memberExpression, ConstantExpression constantExpression) + { + // model => {const}.Member (captured local variable) + if (!_constMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) + { + // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member + var parameterExpression = Expression.Parameter(typeof(object), "capturedLocal"); + var castExpression = + Expression.Convert(parameterExpression, memberExpression.Member.DeclaringType); + var replacementMemberExpression = memberExpression.Update(castExpression); + var replacementExpression = Expression.Lambda>( + replacementMemberExpression, + parameterExpression); + + result = replacementExpression.Compile(); + result = _constMemberAccessCache.GetOrAdd(memberExpression.Member, result); + } + + var capturedLocal = constantExpression.Value; + return _ => result(capturedLocal); + } + + private static Func CompileSlow(Expression> expression) + { + // fallback compilation system - just compile the expression directly + return expression.Compile(); + } + } + } +} From 1ca6ce33775dd74e7f0d930301160e9db4827d3d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 May 2018 17:40:28 -0700 Subject: [PATCH 018/316] setupAction parameter in MvcLocalizationServices.AddLocalizationServices is never used Fixes #7761 --- .../MvcLocalizationMvcBuilderExtensions.cs | 2 +- ...MvcLocalizationMvcCoreBuilderExtensions.cs | 2 +- .../{Internal => }/MvcLocalizationServices.cs | 9 ++-- .../Properties/AssemblyInfo.cs | 6 +++ ...lizationServiceCollectionExtensionsTest.cs | 41 ++++--------------- 5 files changed, 19 insertions(+), 41 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Localization/{Internal => }/MvcLocalizationServices.cs (84%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs rename test/Microsoft.AspNetCore.Mvc.Localization.Test/{Internal => }/MvcLocalizationServiceCollectionExtensionsTest.cs (82%) diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs index ddeb4f25b4..d5c3c6df4c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs @@ -3,7 +3,7 @@ using System; using Microsoft.AspNetCore.Mvc.DataAnnotations; -using Microsoft.AspNetCore.Mvc.Localization.Internal; +using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Localization; diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs index 27b1f90a7f..e0a970ab88 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs @@ -3,7 +3,7 @@ using System; using Microsoft.AspNetCore.Mvc.DataAnnotations; -using Microsoft.AspNetCore.Mvc.Localization.Internal; +using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Localization; diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.cs b/src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs similarity index 84% rename from src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.cs rename to src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs index 4e1f26ebc0..a1b056250b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/Internal/MvcLocalizationServices.cs +++ b/src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs @@ -7,16 +7,16 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Localization; -namespace Microsoft.AspNetCore.Mvc.Localization.Internal +namespace Microsoft.AspNetCore.Mvc.Localization { - public static class MvcLocalizationServices + internal static class MvcLocalizationServices { public static void AddLocalizationServices( IServiceCollection services, LanguageViewLocationExpanderFormat format, Action setupAction) { - AddMvcViewLocalizationServices(services, format, setupAction); + AddMvcViewLocalizationServices(services, format); if (setupAction == null) { @@ -31,8 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Internal // To enable unit testing only 'MVC' specific services public static void AddMvcViewLocalizationServices( IServiceCollection services, - LanguageViewLocationExpanderFormat format, - Action setupAction) + LanguageViewLocationExpanderFormat format) { services.Configure( options => diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..8110c12929 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Localization.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs similarity index 82% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs rename to test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs index 50f4c7626a..f67e5f466d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Internal/MvcLocalizationServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs @@ -5,16 +5,13 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; -using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Localization; -using Microsoft.Extensions.Options; -using Microsoft.Extensions.WebEncoders.Testing; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Localization.Internal +namespace Microsoft.AspNetCore.Mvc.Localization { public class MvcLocalizationServicesTest { @@ -27,8 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Internal // Act MvcLocalizationServices.AddMvcViewLocalizationServices( collection, - LanguageViewLocationExpanderFormat.Suffix, - setupAction: null); + LanguageViewLocationExpanderFormat.Suffix); // Assert AssertContainsSingle(collection, typeof(IHtmlLocalizerFactory), typeof(HtmlLocalizerFactory)); @@ -49,8 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Internal MvcLocalizationServices.AddMvcViewLocalizationServices( collection, - LanguageViewLocationExpanderFormat.Suffix, - setupAction: null); + LanguageViewLocationExpanderFormat.Suffix); AssertContainsSingle(collection, typeof(IHtmlLocalizerFactory), typeof(TestHtmlLocalizerFactory)); AssertContainsSingle(collection, typeof(IHtmlLocalizer<>), typeof(TestHtmlLocalizer<>)); @@ -84,21 +79,10 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Internal public class TestViewLocalizer : IViewLocalizer { - public LocalizedHtmlString this[string name] - { - get - { - throw new NotImplementedException(); - } - } + public LocalizedHtmlString this[string name] => throw new NotImplementedException(); public LocalizedHtmlString this[string name, params object[] arguments] - { - get - { - throw new NotImplementedException(); - } - } + => throw new NotImplementedException(); public LocalizedString GetString(string name) { @@ -123,21 +107,10 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Internal public class TestHtmlLocalizer : IHtmlLocalizer { - public LocalizedHtmlString this[string name] - { - get - { - throw new NotImplementedException(); - } - } + public LocalizedHtmlString this[string name] => throw new NotImplementedException(); public LocalizedHtmlString this[string name, params object[] arguments] - { - get - { - throw new NotImplementedException(); - } - } + => throw new NotImplementedException(); public LocalizedString GetString(string name) { From 57eb52ad470feed0b9ab2d2e9075c719e46f490b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 13 May 2018 14:17:20 -0700 Subject: [PATCH 019/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 152 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b6b78abcbd..fb7f89a798 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,90 +5,90 @@ 0.9.9 0.10.13 - 2.2.0-preview1-17048 - 2.2.0-a-preview1-a2-16496 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 + 2.2.0-preview1-17051 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 5.2.4 - 2.8.0-beta3 - 2.8.0-beta3 - 2.2.0-preview1-34136 + 2.8.0 + 2.8.0 + 2.2.0-preview1-34184 1.7.0 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-26424-04 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-26509-06 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 2.0.0 2.1.0-rc1 - 2.2.0-preview1-26502-01 - 2.2.0-preview1-34136 - 2.2.0-preview1-34136 + 2.2.0-preview1-26509-06 + 2.2.0-preview1-34184 + 2.2.0-preview1-34184 15.6.1 4.7.49 2.0.3 1.0.1 - 4.5.0-preview3-26423-04 - 4.5.0-preview3-26423-04 - 4.5.0-preview3-26423-04 + 4.6.0-preview1-26508-04 + 4.6.0-preview1-26508-04 + 4.6.0-preview1-26508-04 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 2573a03995..89629b454c 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17048 -commithash:de14a0ee5fb48508ee8a29c14280a2928f8dabf8 +version:2.2.0-preview1-17051 +commithash:253c3a480063bc3abaa5cde42f6e27b58457ef9b From d9f035ad7c4693c314e293f21bfef7ed002dc8e2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 10 May 2018 07:18:49 -0700 Subject: [PATCH 020/316] CacheTagHelper should be able to vary by culture Fixes #3398 --- .../Cache/CacheTagKey.cs | 61 +++++- .../CacheTagHelperBase.cs | 14 +- .../HtmlGenerationWithCultureTest.cs | 173 ++++++++++++++++++ .../CacheTagKeyTest.cs | 154 +++++++++++++++- .../Pages/CacheTagHelper_VaryByCulture.cshtml | 14 ++ .../Pages/_ViewImports.cshtml | 1 + .../StartupWithCultureReplace.cs | 46 +++++ 7 files changed, 449 insertions(+), 14 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs create mode 100644 test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml create mode 100644 test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml create mode 100644 test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs index f1d6730ae0..3d1912cba0 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; @@ -32,6 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache private const string VaryByRouteName = "VaryByRoute"; private const string VaryByCookieName = "VaryByCookie"; private const string VaryByUserName = "VaryByUser"; + private const string VaryByCulture = "VaryByCulture"; private readonly string _prefix; private readonly string _varyBy; @@ -43,7 +45,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache private readonly IList> _routeValues; private readonly IList> _cookies; private readonly bool _varyByUser; + private readonly bool _varyByCulture; private readonly string _username; + private readonly CultureInfo _requestCulture; + private readonly CultureInfo _requestUICulture; private string _generatedKey; private int? _hashcode; @@ -87,11 +92,18 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache _queries = ExtractCollection(tagHelper.VaryByQuery, request.Query, QueryAccessor); _routeValues = ExtractCollection(tagHelper.VaryByRoute, tagHelper.ViewContext.RouteData.Values, RouteValueAccessor); _varyByUser = tagHelper.VaryByUser; + _varyByCulture = tagHelper.VaryByCulture; if (_varyByUser) { _username = httpContext.User?.Identity?.Name; } + + if (_varyByCulture) + { + _requestCulture = CultureInfo.CurrentCulture; + _requestUICulture = CultureInfo.CurrentUICulture; + } } // Internal for unit testing. @@ -137,6 +149,17 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache .Append(_username); } + if (_varyByCulture) + { + builder + .Append(CacheKeyTokenSeparator) + .Append(VaryByCulture) + .Append(CacheKeyTokenSeparator) + .Append(_requestCulture) + .Append(CacheKeyTokenSeparator) + .Append(_requestUICulture); + } + _generatedKey = builder.ToString(); return _generatedKey; @@ -164,13 +187,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache /// public override bool Equals(object obj) { - var other = obj as CacheTagKey; - if (other == null) + if (obj is CacheTagKey other) { - return false; + return Equals(other); } - return Equals(other); + return false; } /// @@ -185,8 +207,26 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache AreSame(_headers, other._headers) && AreSame(_queries, other._queries) && AreSame(_routeValues, other._routeValues) && - _varyByUser == other._varyByUser && - (!_varyByUser || string.Equals(other._username, _username, StringComparison.Ordinal)); + (_varyByUser == other._varyByUser && + (!_varyByUser || string.Equals(other._username, _username, StringComparison.Ordinal))) && + CultureEquals(); + + bool CultureEquals() + { + if (_varyByCulture != other._varyByCulture) + { + return false; + } + + if (!_varyByCulture) + { + // Neither has culture set. + return true; + } + + return _requestCulture.Equals(other._requestCulture) && + _requestUICulture.Equals(other._requestUICulture); + } } /// @@ -211,6 +251,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache hashCodeCombiner.Add(_expiresSliding); hashCodeCombiner.Add(_varyBy, StringComparer.Ordinal); hashCodeCombiner.Add(_username, StringComparer.Ordinal); + hashCodeCombiner.Add(_requestCulture); + hashCodeCombiner.Add(_requestUICulture); CombineCollectionHashCode(hashCodeCombiner, VaryByCookieName, _cookies); CombineCollectionHashCode(hashCodeCombiner, VaryByHeaderName, _headers); @@ -222,7 +264,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache return _hashcode.Value; } - private static IList> ExtractCollection(string keys, TSourceCollection collection, Func accessor) + private static IList> ExtractCollection( + string keys, + TSourceCollection collection, + Func accessor) { if (string.IsNullOrEmpty(keys)) { @@ -323,4 +368,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache return true; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs index ca7dfdcb70..9ebd8a7717 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Globalization; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; @@ -20,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string VaryByRouteAttributeName = "vary-by-route"; private const string VaryByCookieAttributeName = "vary-by-cookie"; private const string VaryByUserAttributeName = "vary-by-user"; + private const string VaryByCultureAttributeName = "vary-by-culture"; private const string ExpiresOnAttributeName = "expires-on"; private const string ExpiresAfterAttributeName = "expires-after"; private const string ExpiresSlidingAttributeName = "expires-sliding"; @@ -93,6 +95,16 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(VaryByUserAttributeName)] public bool VaryByUser { get; set; } + /// + /// Gets or sets a value that determines if the cached result is to be varied by request culture. + /// + /// Setting this to true would result in the result to be varied by + /// and . + /// + /// + [HtmlAttributeName(VaryByCultureAttributeName)] + public bool VaryByCulture { get; set; } + /// /// Gets or sets the exact the cache entry should be evicted. /// @@ -117,4 +129,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(EnabledAttributeName)] public bool Enabled { get; set; } = true; } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs new file mode 100644 index 0000000000..9b1d1f5cbe --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs @@ -0,0 +1,173 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net.Http; +using System.Threading.Tasks; +using AngleSharp.Dom; +using AngleSharp.Dom.Html; +using HtmlGenerationWebSite; +using Microsoft.AspNetCore.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class HtmlGenerationWithCultureTest : IClassFixture> + { + public HtmlGenerationWithCultureTest(MvcTestFixture fixture) + { + var factory = fixture.WithWebHostBuilder(builder => builder.UseStartup()); + Client = factory.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact] + public async Task CacheTagHelper_AllowsVaryingByCulture() + { + // Arrange + string culture; + string correlationId; + string cachedCorrelationId; + + // Act - 1 + var document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&correlationId=10"); + ReadValuesFromDocument(); + + // Assert - 1 + Assert.Equal("fr-FR", culture); + Assert.Equal("10", correlationId); + Assert.Equal("10", cachedCorrelationId); + + // Act - 2 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=en-GB&correlationId=11"); + ReadValuesFromDocument(); + + // Assert - 2 + Assert.Equal("en-GB", culture); + Assert.Equal("11", correlationId); + Assert.Equal("11", cachedCorrelationId); + + // Act - 3 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&correlationId=14"); + ReadValuesFromDocument(); + + // Assert - 3 + Assert.Equal("fr-FR", culture); + Assert.Equal("14", correlationId); + // Verify we're reading a cached value + Assert.Equal("10", cachedCorrelationId); + + void ReadValuesFromDocument() + { + culture = QuerySelector(document, "#culture").TextContent; + correlationId = QuerySelector(document, "#correlation-id").TextContent; + cachedCorrelationId = QuerySelector(document, "#cached-correlation-id").TextContent; + } + } + + [Fact] + public async Task CacheTagHelper_AllowsVaryingByUICulture() + { + // Arrange + string culture; + string uiCulture; + string correlationId; + string cachedCorrelationId; + + // Act - 1 + var document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&ui-culture=fr-FR&correlationId=10"); + ReadValuesFromDocument(); + + // Assert - 1 + Assert.Equal("fr-FR", culture); + Assert.Equal("fr-FR", uiCulture); + Assert.Equal("10", correlationId); + Assert.Equal("10", cachedCorrelationId); + + // Act - 2 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&ui-culture=fr-CA&correlationId=11"); + ReadValuesFromDocument(); + + // Assert - 2 + Assert.Equal("fr-FR", culture); + Assert.Equal("fr-CA", uiCulture); + Assert.Equal("11", correlationId); + Assert.Equal("11", cachedCorrelationId); + + // Act - 3 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&ui-culture=fr-FR&correlationId=14"); + ReadValuesFromDocument(); + + // Assert - 3 + Assert.Equal("fr-FR", culture); + Assert.Equal("fr-FR", uiCulture); + Assert.Equal("14", correlationId); + // Verify we're reading a cached value + Assert.Equal("10", cachedCorrelationId); + + void ReadValuesFromDocument() + { + culture = QuerySelector(document, "#culture").TextContent; + uiCulture = QuerySelector(document, "#ui-culture").TextContent; + correlationId = QuerySelector(document, "#correlation-id").TextContent; + cachedCorrelationId = QuerySelector(document, "#cached-correlation-id").TextContent; + } + } + + [Fact] + public async Task CacheTagHelper_VaryByCultureComposesWithOtherVaryByOptions() + { + // Arrange + string culture; + string correlationId; + string cachedCorrelationId; + + // Act - 1 + var document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&correlationId=10"); + ReadValuesFromDocument(); + + // Assert - 1 + Assert.Equal("fr-FR", culture); + Assert.Equal("10", correlationId); + Assert.Equal("10", cachedCorrelationId); + + // Act - 2 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&correlationId=11&varyByQueryKey=new-key"); + ReadValuesFromDocument(); + + // Assert - 2 + // vary-by-query should produce a new cached value. + Assert.Equal("fr-FR", culture); + Assert.Equal("11", correlationId); + Assert.Equal("11", cachedCorrelationId); + + // Act - 3 + document = await Client.GetHtmlDocumentAsync("/CacheTagHelper_VaryByCulture?culture=fr-Fr&correlationId=14"); + ReadValuesFromDocument(); + + // Assert - 3 + Assert.Equal("fr-FR", culture); + Assert.Equal("14", correlationId); + Assert.Equal("10", cachedCorrelationId); + + void ReadValuesFromDocument() + { + culture = QuerySelector(document, "#culture").TextContent; + correlationId = QuerySelector(document, "#correlation-id").TextContent; + cachedCorrelationId = QuerySelector(document, "#cached-correlation-id").TextContent; + } + } + + private static IElement QuerySelector(IHtmlDocument document, string selector) + { + var element = document.QuerySelector(selector); + if (element == null) + { + throw new ArgumentException($"Document does not contain element that matches the selector {selector}: " + Environment.NewLine + document.DocumentElement.OuterHtml); + } + + return element; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs index 7cd1718b20..36ce9d6216 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.WebEncoders.Testing; using Moq; @@ -345,6 +346,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expected, key); } + [Fact] + [ReplaceCulture("fr-FR", "es-ES")] + public void GenerateKey_UsesCultureAndUICultureName_IfVaryByCulture_IsSet() + { + // Arrange + var expected = "CacheTagHelper||testid||VaryByCulture||fr-FR||es-ES"; + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of()), new HtmlTestEncoder()) + { + ViewContext = GetViewContext(), + VaryByCulture = true + }; + + // Act + var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); + var key = cacheTagKey.GenerateKey(); + + // Assert + Assert.Equal(expected, key); + } + [Fact] public void GenerateKey_WithMultipleVaryByOptions_CreatesCombinedKey() { @@ -371,15 +393,137 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expected, key); } + [Fact] + [ReplaceCulture("zh", "zh-Hans")] + public void GenerateKey_WithVaryByCulture_ComposesWithOtherOptions() + { + // Arrange + var expected = "CacheTagHelper||testid||VaryBy||custom-value||" + + "VaryByHeader(content-type||text/html)||VaryByCulture||zh||zh-Hans"; + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of()), new HtmlTestEncoder()) + { + ViewContext = GetViewContext(), + VaryByCulture = true, + VaryByHeader = "content-type", + VaryBy = "custom-value" + }; + cacheTagHelper.ViewContext.HttpContext.Request.Headers["Content-Type"] = "text/html"; + + // Act + var cacheTagKey = new CacheTagKey(cacheTagHelper, tagHelperContext); + var key = cacheTagKey.GenerateKey(); + + // Assert + Assert.Equal(expected, key); + } + + [Fact] + public void Equality_ReturnsFalse_WhenVaryByCultureIsTrue_AndCultureIsDifferent() + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of()), new HtmlTestEncoder()) + { + ViewContext = GetViewContext(), + VaryByCulture = true, + }; + + // Act + CacheTagKey key1; + using (new CultureReplacer("fr-FR")) + { + key1 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + + CacheTagKey key2; + using (new CultureReplacer("es-ES")) + { + key2 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + var equals = key1.Equals(key2); + var hashCode1 = key1.GetHashCode(); + var hashCode2 = key2.GetHashCode(); + + // Assert + Assert.False(equals, "CacheTagKeys must not be equal"); + Assert.NotEqual(hashCode1, hashCode2); + } + + [Fact] + public void Equality_ReturnsFalse_WhenVaryByCultureIsTrue_AndUICultureIsDifferent() + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of()), new HtmlTestEncoder()) + { + ViewContext = GetViewContext(), + VaryByCulture = true, + }; + + // Act + CacheTagKey key1; + using (new CultureReplacer("fr", "fr-FR")) + { + key1 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + + CacheTagKey key2; + using (new CultureReplacer("fr", "fr-CA")) + { + key2 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + var equals = key1.Equals(key2); + var hashCode1 = key1.GetHashCode(); + var hashCode2 = key2.GetHashCode(); + + // Assert + Assert.False(equals, "CacheTagKeys must not be equal"); + Assert.NotEqual(hashCode1, hashCode2); + } + + [Fact] + public void Equality_ReturnsTrue_WhenVaryByCultureIsTrue_AndCultureIsSame() + { + // Arrange + var tagHelperContext = GetTagHelperContext(); + var cacheTagHelper = new CacheTagHelper(new CacheTagHelperMemoryCacheFactory(Mock.Of()), new HtmlTestEncoder()) + { + ViewContext = GetViewContext(), + VaryByCulture = true, + }; + + // Act + CacheTagKey key1; + CacheTagKey key2; + using (new CultureReplacer("fr-FR", "fr-FR")) + { + key1 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + + using (new CultureReplacer("fr-fr", "fr-fr")) + { + key2 = new CacheTagKey(cacheTagHelper, tagHelperContext); + } + + var equals = key1.Equals(key2); + var hashCode1 = key1.GetHashCode(); + var hashCode2 = key2.GetHashCode(); + + // Assert + Assert.True(equals, "CacheTagKeys must be equal"); + Assert.Equal(hashCode1, hashCode2); + } + private static ViewContext GetViewContext() { var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); return new ViewContext(actionContext, - Mock.Of(), - new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), - Mock.Of(), - TextWriter.Null, - new HtmlHelperOptions()); + Mock.Of(), + new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()), + Mock.Of(), + TextWriter.Null, + new HtmlHelperOptions()); } private static TagHelperContext GetTagHelperContext(string id = "testid") diff --git a/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml b/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml new file mode 100644 index 0000000000..bdf54e5728 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml @@ -0,0 +1,14 @@ +@page +@using System.Globalization +@functions +{ + [BindProperty(SupportsGet = true)] + public int CorrelationId { get; set; } +} + +

@CultureInfo.CurrentCulture

+

@CultureInfo.CurrentUICulture

+@CorrelationId + + @CorrelationId + diff --git a/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml b/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..a757b413b9 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml @@ -0,0 +1 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs b/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs new file mode 100644 index 0000000000..ea197babb3 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs @@ -0,0 +1,46 @@ +// 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.Globalization; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Localization; +using Microsoft.Extensions.DependencyInjection; + +namespace HtmlGenerationWebSite +{ + public class StartupWithCultureReplace + { + private readonly Startup Startup = new Startup(); + + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + services.AddLocalization(); + Startup.ConfigureServices(services); + } + + public void Configure(IApplicationBuilder app) + { + app.UseRequestLocalization(options => + { + options.SupportedCultures.Add(new CultureInfo("fr-FR")); + options.SupportedCultures.Add(new CultureInfo("en-GB")); + + options.SupportedUICultures.Add(new CultureInfo("fr-FR")); + options.SupportedUICultures.Add(new CultureInfo("fr-CA")); + options.SupportedUICultures.Add(new CultureInfo("en-GB")); + }); + + Startup.Configure(app); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseKestrel() + .UseIISIntegration(); + } +} From 19d82928ba2c04daa814e2d1b9c0779c4952064b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Tue, 15 May 2018 11:36:47 -0700 Subject: [PATCH 021/316] Making Razor compilation cache replaceable (#7780) --- .../IViewCompilationMemoryCacheProvider.cs | 15 +++++++++++++++ .../RazorViewCompilationMemoryCacheProvider.cs | 12 ++++++++++++ .../MvcRazorMvcCoreBuilderExtensions.cs | 1 + .../Internal/RazorViewCompiler.cs | 6 ++++-- .../Internal/RazorViewCompilerProvider.cs | 4 ++++ .../Internal/RazorViewCompilerProviderTest.cs | 2 ++ .../Internal/RazorViewCompilerTest.cs | 3 ++- 7 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs new file mode 100644 index 0000000000..5abbd550f3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs @@ -0,0 +1,15 @@ +// 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.Extensions.Caching.Memory; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + /// + /// Provides an instance of that is used to store compiled Razor views. + /// + public interface IViewCompilationMemoryCacheProvider + { + IMemoryCache CompilationMemoryCache { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs new file mode 100644 index 0000000000..9a11db0b3f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs @@ -0,0 +1,12 @@ +// 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.Extensions.Caching.Memory; + +namespace Microsoft.AspNetCore.Mvc.Razor.Compilation +{ + internal class RazorViewCompilationMemoryCacheProvider : IViewCompilationMemoryCacheProvider + { + IMemoryCache IViewCompilationMemoryCacheProvider.CompilationMemoryCache { get; } = new MemoryCache(new MemoryCacheOptions()); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 2eb17bc862..7dddf090bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -172,6 +172,7 @@ namespace Microsoft.Extensions.DependencyInjection return viewEngine; }); services.TryAddSingleton(); + services.TryAddSingleton(); // In the default scenario the following services are singleton by virtue of being initialized as part of // creating the singleton RazorViewEngine instance. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs index 93b871cc76..1f4d30b159 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs @@ -34,9 +34,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly IFileProvider _fileProvider; private readonly RazorProjectEngine _projectEngine; private readonly Action _compilationCallback; + private readonly IMemoryCache _cache; private readonly ILogger _logger; private readonly CSharpCompiler _csharpCompiler; - private readonly IMemoryCache _cache; public RazorViewCompiler( IFileProvider fileProvider, @@ -44,6 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal CSharpCompiler csharpCompiler, Action compilationCallback, IList precompiledViews, + IMemoryCache cache, ILogger logger) { if (fileProvider == null) @@ -82,11 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _compilationCallback = compilationCallback; _logger = logger; + _normalizedPathCache = new ConcurrentDictionary(StringComparer.Ordinal); // This is our L0 cache, and is a durable store. Views migrate into the cache as they are requested // from either the set of known precompiled views, or by being compiled. - _cache = new MemoryCache(new MemoryCacheOptions()); + _cache = cache; // We need to validate that the all of the precompiled views are unique by path (case-insenstive). // We do this because there's no good way to canonicalize paths on windows, and it will create diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs index c705a88aac..4ef67017d1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly ApplicationPartManager _applicationPartManager; private readonly IRazorViewEngineFileProviderAccessor _fileProviderAccessor; private readonly CSharpCompiler _csharpCompiler; + private readonly IViewCompilationMemoryCacheProvider _compilationMemoryCacheProvider; private readonly RazorViewEngineOptions _viewEngineOptions; private readonly ILogger _logger; private readonly Func _createCompiler; @@ -32,12 +33,14 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal IRazorViewEngineFileProviderAccessor fileProviderAccessor, CSharpCompiler csharpCompiler, IOptions viewEngineOptionsAccessor, + IViewCompilationMemoryCacheProvider compilationMemoryCacheProvider, ILoggerFactory loggerFactory) { _applicationPartManager = applicationPartManager; _razorProjectEngine = razorProjectEngine; _fileProviderAccessor = fileProviderAccessor; _csharpCompiler = csharpCompiler; + _compilationMemoryCacheProvider = compilationMemoryCacheProvider; _viewEngineOptions = viewEngineOptionsAccessor.Value; _logger = loggerFactory.CreateLogger(); @@ -74,6 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _csharpCompiler, _viewEngineOptions.CompilationCallback, feature.ViewDescriptors, + _compilationMemoryCacheProvider.CompilationMemoryCache, _logger); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs index c00237b990..9b3656aaee 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; @@ -39,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal accessor, new CSharpCompiler(referenceManager, Mock.Of()), options, + new RazorViewCompilationMemoryCacheProvider(), NullLoggerFactory.Instance); // Act & Assert diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs index c4df0280aa..929bf63b93 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Emit; +using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; @@ -875,7 +876,7 @@ this should fail"; Action compilationCallback, IList precompiledViews, Func compile = null) : - base(fileProvider, projectEngine, csharpCompiler, compilationCallback, precompiledViews, NullLogger.Instance) + base(fileProvider, projectEngine, csharpCompiler, compilationCallback, precompiledViews, new MemoryCache(new MemoryCacheOptions()), NullLogger.Instance) { Compile = compile; if (Compile == null) From b0796ce8a843ed86b32d008d5567fdb89308eba5 Mon Sep 17 00:00:00 2001 From: Artak <34246760+mkArtakMSFT@users.noreply.github.com> Date: Tue, 15 May 2018 14:09:30 -0700 Subject: [PATCH 022/316] Update the `Stes to reproduece` section header (#7767) --- .github/ISSUE_TEMPLATE | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index d7a9ec10b7..c4282ab656 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1,7 +1,7 @@ #### Is this a Bug or Feature request?: -#### Steps to reproduce or link to a repro project: +#### Steps to reproduce (preferrably a link to a GitHub repo with a repro project): #### Description of the problem: @@ -22,4 +22,4 @@ 3. If you believe you have an issue that affects the security of the platform please do not create an issue, instead email your issue to 'secure@microsoft.com'. Your report may be eligible for our bug bounty. More info: https://technet.microsoft.com/en-us/mt764065.aspx ---> \ No newline at end of file +--> From d80471ad15a156d8be9f369548deeae7194854e9 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 15 May 2018 12:55:26 -0700 Subject: [PATCH 023/316] Added logging to model binding requestpredicate shortcircuit --- .../Internal/MvcCoreLoggerExtensions.cs | 51 +++++++++++++++++++ .../ModelBinding/ParameterBinder.cs | 2 + 2 files changed, 53 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 2050911090..e81af7df95 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -109,6 +109,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static readonly Action _foundNoValueForPropertyInRequest; private static readonly Action _foundNoValueForParameterInRequest; private static readonly Action _foundNoValueInRequest; + private static readonly Action _parameterBinderRequestPredicateShortCircuitOfProperty; + private static readonly Action _parameterBinderRequestPredicateShortCircuitOfParameter; private static readonly Action _noPublicSettableProperties; private static readonly Action _cannotBindToComplexType; private static readonly Action _cannotBindToFilesCollectionDueToUnsupportedContentType; @@ -636,6 +638,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal LogLevel.Debug, 46, "Could not find a value in the request with name '{ModelName}' of type '{ModelType}'."); + + _parameterBinderRequestPredicateShortCircuitOfProperty = LoggerMessage.Define( + LogLevel.Debug, + 47, + "Skipped binding property '{PropertyContainerType}.{PropertyName}' since it's binding information disallowed it for the current request."); + + _parameterBinderRequestPredicateShortCircuitOfParameter = LoggerMessage.Define( + LogLevel.Debug, + 48, + "Skipped binding parameter '{ParameterName}' since it's binding information disallowed it for the current request."); } public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable outputFormatters) @@ -1526,6 +1538,45 @@ namespace Microsoft.AspNetCore.Mvc.Internal null); } + public static void ParameterBinderRequestPredicateShortCircuit( + this ILogger logger, + ModelMetadata modelMetadata, + ParameterDescriptor parameter) + { + switch (modelMetadata.MetadataKind) + { + case ModelMetadataKind.Parameter: + _parameterBinderRequestPredicateShortCircuitOfParameter( + logger, + modelMetadata.ParameterName, + null); + break; + case ModelMetadataKind.Property: + _parameterBinderRequestPredicateShortCircuitOfProperty( + logger, + modelMetadata.ContainerType, + modelMetadata.PropertyName, + null); + break; + case ModelMetadataKind.Type: + if (parameter is ControllerParameterDescriptor controllerParameterDescriptor) + { + _parameterBinderRequestPredicateShortCircuitOfParameter( + logger, + controllerParameterDescriptor.ParameterInfo.Name, + null); + } + else + { + // Likely binding a page handler parameter. Due to various special cases, parameter.Name may + // be empty. No way to determine actual name. This case is less likely than for binding logging + // (above). Should occur only with a legacy IModelMetadataProvider implementation. + _parameterBinderRequestPredicateShortCircuitOfParameter(logger, parameter.Name, null); + } + break; + } + } + private static void LogFilterExecutionPlan( ILogger logger, string filterType, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs index 90a2458f2a..542ae37902 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -205,6 +205,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false) { + Logger.ParameterBinderRequestPredicateShortCircuit(metadata, parameter); + return ModelBindingResult.Failed(); } From 4472c00c6f01f2be38b60b7695fa92ad57a37a59 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 17 May 2018 11:03:06 -0700 Subject: [PATCH 024/316] PR feedback fixes for logging requestpredicate shortcircuit logging --- .../Internal/MvcCoreLoggerExtensions.cs | 25 ++++++++++--------- .../ModelBinding/ParameterBinder.cs | 15 ++++++----- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index e81af7df95..db626eb4a5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -642,12 +642,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal _parameterBinderRequestPredicateShortCircuitOfProperty = LoggerMessage.Define( LogLevel.Debug, 47, - "Skipped binding property '{PropertyContainerType}.{PropertyName}' since it's binding information disallowed it for the current request."); + "Skipped binding property '{PropertyContainerType}.{PropertyName}' since its binding information disallowed it for the current request."); _parameterBinderRequestPredicateShortCircuitOfParameter = LoggerMessage.Define( LogLevel.Debug, 48, - "Skipped binding parameter '{ParameterName}' since it's binding information disallowed it for the current request."); + "Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request."); } public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable outputFormatters) @@ -1324,14 +1324,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static void AttemptingToBindParameterOrProperty( this ILogger logger, ParameterDescriptor parameter, - ModelBindingContext bindingContext) + ModelMetadata modelMetadata) { if (!logger.IsEnabled(LogLevel.Debug)) { return; } - var modelMetadata = bindingContext.ModelMetadata; switch (modelMetadata.MetadataKind) { case ModelMetadataKind.Parameter: @@ -1367,14 +1366,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static void DoneAttemptingToBindParameterOrProperty( this ILogger logger, ParameterDescriptor parameter, - ModelBindingContext bindingContext) + ModelMetadata modelMetadata) { if (!logger.IsEnabled(LogLevel.Debug)) { return; } - var modelMetadata = bindingContext.ModelMetadata; switch (modelMetadata.MetadataKind) { case ModelMetadataKind.Parameter: @@ -1410,14 +1408,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static void AttemptingToValidateParameterOrProperty( this ILogger logger, ParameterDescriptor parameter, - ModelBindingContext bindingContext) + ModelMetadata modelMetadata) { if (!logger.IsEnabled(LogLevel.Debug)) { return; } - var modelMetadata = bindingContext.ModelMetadata; switch (modelMetadata.MetadataKind) { case ModelMetadataKind.Parameter: @@ -1454,14 +1451,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static void DoneAttemptingToValidateParameterOrProperty( this ILogger logger, ParameterDescriptor parameter, - ModelBindingContext bindingContext) + ModelMetadata modelMetadata) { if (!logger.IsEnabled(LogLevel.Debug)) { return; } - var modelMetadata = bindingContext.ModelMetadata; switch (modelMetadata.MetadataKind) { case ModelMetadataKind.Parameter: @@ -1540,9 +1536,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal public static void ParameterBinderRequestPredicateShortCircuit( this ILogger logger, - ModelMetadata modelMetadata, - ParameterDescriptor parameter) + ParameterDescriptor parameter, + ModelMetadata modelMetadata) { + if (!logger.IsEnabled(LogLevel.Debug)) + { + return; + } + switch (modelMetadata.MetadataKind) { case ModelMetadataKind.Parameter: diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs index 542ae37902..973058609b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -203,10 +203,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding throw new ArgumentNullException(nameof(metadata)); } + Logger.AttemptingToBindParameterOrProperty(parameter, metadata); + if (parameter.BindingInfo?.RequestPredicate?.Invoke(actionContext) == false) { - Logger.ParameterBinderRequestPredicateShortCircuit(metadata, parameter); - + Logger.ParameterBinderRequestPredicateShortCircuit(parameter, metadata); return ModelBindingResult.Failed(); } @@ -218,8 +219,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding parameter.Name); modelBindingContext.Model = value; - Logger.AttemptingToBindParameterOrProperty(parameter, modelBindingContext); - var parameterModelName = parameter.BindingInfo?.BinderModelName ?? metadata.BinderModelName; if (parameterModelName != null) { @@ -239,14 +238,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding await modelBinder.BindModelAsync(modelBindingContext); - Logger.DoneAttemptingToBindParameterOrProperty(parameter, modelBindingContext); + Logger.DoneAttemptingToBindParameterOrProperty(parameter, metadata); var modelBindingResult = modelBindingContext.Result; if (_mvcOptions.AllowValidatingTopLevelNodes && _objectModelValidator is ObjectModelValidator baseObjectValidator) { - Logger.AttemptingToValidateParameterOrProperty(parameter, modelBindingContext); + Logger.AttemptingToValidateParameterOrProperty(parameter, metadata); EnforceBindRequiredAndValidate( baseObjectValidator, @@ -256,7 +255,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding modelBindingContext, modelBindingResult); - Logger.DoneAttemptingToValidateParameterOrProperty(parameter, modelBindingContext); + Logger.DoneAttemptingToValidateParameterOrProperty(parameter, metadata); } else { @@ -321,7 +320,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // and we ended up with an empty prefix. modelName = modelBindingContext.FieldName; } - + // Run validation, we expect this to validate [Required]. baseObjectValidator.Validate( actionContext, From a736441ca5ac7524f4ed0d9c33370becb523c778 Mon Sep 17 00:00:00 2001 From: Pavel Krymets Date: Fri, 18 May 2018 10:26:42 -0700 Subject: [PATCH 025/316] Enable analyzers (#7789) --- build/dependencies.props | 1 + src/Directory.Build.props | 1 + .../Controllers/ControllerFactoryProvider.cs | 5 ++++- .../Controllers/DefaultControllerActivator.cs | 2 ++ .../Controllers/DefaultControllerFactory.cs | 5 ++++- src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs | 4 +++- .../ModelBinding/FormValueProvider.cs | 2 ++ .../ModelBinding/JQueryValueProvider.cs | 2 ++ .../ModelBinding/ObjectModelValidator.cs | 2 ++ .../ModelBinding/QueryStringValueProvider.cs | 2 ++ .../ModelBinding/RouteValueProvider.cs | 4 +++- .../ModelBinding/Validation/ValidationVisitor.cs | 6 ++++++ src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs | 7 ++++++- src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs | 2 ++ .../PartialTagHelper.cs | 5 ++++- src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs | 2 ++ .../ViewComponents/DefaultViewComponentActivator.cs | 6 ++++-- .../ViewComponents/DefaultViewComponentHelper.cs | 5 ++++- .../ViewComponents/DefaultViewComponentInvoker.cs | 2 ++ .../ViewComponents/DefaultViewComponentInvokerFactory.cs | 2 ++ .../ViewFeatures/DefaultValidationHtmlAttributeProvider.cs | 5 ++++- .../ViewFeatures/HtmlHelper.cs | 2 ++ .../ViewFeatures/HtmlHelperOfT.cs | 7 ++++++- .../ViewFeatures/ModelExpressionProvider.cs | 5 ++++- 24 files changed, 74 insertions(+), 12 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index fb7f89a798..6384b06ed1 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,6 +5,7 @@ 0.9.9 0.10.13 + 2.2.0-a-preview1-aan-16524 2.2.0-preview1-17051 2.2.0-preview1-34184 2.2.0-preview1-34184 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 4b89a431e7..ab4e538839 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -3,5 +3,6 @@ +
diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs index e11ad85096..8b70db1f79 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs @@ -19,7 +19,10 @@ namespace Microsoft.AspNetCore.Mvc.Controllers public ControllerFactoryProvider( IControllerActivatorProvider activatorProvider, IControllerFactory controllerFactory, - IEnumerable propertyActivators) +#pragma warning disable PUB0001 // Pubternal type in public API + IEnumerable propertyActivators +#pragma warning restore PUB0001 + ) { if (activatorProvider == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs index 8d53e715e3..9de77f768c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs @@ -18,7 +18,9 @@ namespace Microsoft.AspNetCore.Mvc.Controllers /// Creates a new . /// /// The . +#pragma warning disable PUB0001 // Pubternal type in public API public DefaultControllerActivator(ITypeActivatorCache typeActivatorCache) +#pragma warning restore PUB0001 { if (typeActivatorCache == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs index 3a2627d1ec..8c10083810 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs @@ -29,7 +29,10 @@ namespace Microsoft.AspNetCore.Mvc.Controllers /// public DefaultControllerFactory( IControllerActivator controllerActivator, - IEnumerable propertyActivators) +#pragma warning disable PUB0001 // Pubternal type in public API + IEnumerable propertyActivators +#pragma warning restore PUB0001 + ) { if (controllerActivator == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs index e72fccac65..ea332084ca 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters throw new ArgumentException(Resources.FormatArgument_InvalidOffsetLength(nameof(offset), nameof(length))); } } - + _parameterParser = default(MediaTypeParameterParser); var typeLength = GetTypeLength(mediaType, offset, out var type); @@ -393,7 +393,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// The media type to parse. /// The position at which the parsing starts. /// The parsed media type with its associated quality. +#pragma warning disable PUB0001 // Pubternal type in public API public static MediaTypeSegmentWithQuality CreateMediaTypeSegmentWithQuality(string mediaType, int start) +#pragma warning restore PUB0001 { var parsedMediaType = new MediaType(mediaType, start, length: null); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs index 0e347e99d7..da13af979e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs @@ -46,7 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public CultureInfo Culture => _culture; +#pragma warning disable PUB0001 // Pubternal type in public API protected PrefixContainer PrefixContainer +#pragma warning restore PUB0001 { get { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs index 659a943934..6ae1c84377 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs @@ -52,7 +52,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public CultureInfo Culture { get; } /// +#pragma warning disable PUB0001 // Pubternal type in public API protected PrefixContainer PrefixContainer +#pragma warning restore PUB0001 { get { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs index 0b89489a5f..6c34f35c2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs @@ -101,7 +101,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public abstract ValidationVisitor GetValidationVisitor( ActionContext actionContext, IModelValidatorProvider validatorProvider, +#pragma warning disable PUB0001 // Pubternal type in public API ValidatorCache validatorCache, +#pragma warning restore PUB0001 IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs index a905d72752..e7d62e3852 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs @@ -46,7 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public CultureInfo Culture => _culture; +#pragma warning disable PUB0001 // Pubternal type in public API protected PrefixContainer PrefixContainer +#pragma warning restore PUB0001 { get { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs index 72f556c1c0..6c9a04e281 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } /// - /// Creates a new . + /// Creates a new . /// /// The of the data. /// The values. @@ -57,7 +57,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Culture = culture; } +#pragma warning disable PUB0001 // Pubternal type in public API protected PrefixContainer PrefixContainer +#pragma warning restore PUB0001 { get { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index 499959df27..f8c4c02dd7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -26,7 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation public ValidationVisitor( ActionContext actionContext, IModelValidatorProvider validatorProvider, +#pragma warning disable PUB0001 // Pubternal type in public API ValidatorCache validatorCache, +#pragma warning restore PUB0001 IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) { @@ -58,11 +60,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation protected IModelValidatorProvider ValidatorProvider { get; } protected IModelMetadataProvider MetadataProvider { get; } +#pragma warning disable PUB0001 // Pubternal type in public API protected ValidatorCache Cache { get; } +#pragma warning restore PUB0001 protected ActionContext Context { get; } protected ModelStateDictionary ModelState { get; } protected ValidationStateDictionary ValidationState { get; } +#pragma warning disable PUB0001 // Pubternal type in public API protected ValidationStack CurrentPath { get; } +#pragma warning restore PUB0001 protected object Container { get; set; } protected string Key { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs index 4a7bb989b7..09beddee8a 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs @@ -43,7 +43,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// The factory containing the private instance /// used by the . /// The to use. - public CacheTagHelper(CacheTagHelperMemoryCacheFactory factory, HtmlEncoder htmlEncoder) : base(htmlEncoder) + public CacheTagHelper( +#pragma warning disable PUB0001 // Pubternal type in public API + CacheTagHelperMemoryCacheFactory factory, +#pragma warning restore PUB0001 + HtmlEncoder htmlEncoder) + : base(htmlEncoder) { MemoryCache = factory.Cache; } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs index 84655b04be..6c1f59f515 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs @@ -204,7 +204,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers protected JavaScriptEncoder JavaScriptEncoder { get; } // Internal for ease of use when testing. +#pragma warning disable PUB0001 // Pubternal type in public API protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; } +#pragma warning restore PUB0001 // Shared writer for determining the string content of a TagHelperAttribute's Value. private StringWriter StringWriter diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs index b8451f663e..1a463966cf 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs @@ -32,7 +32,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public PartialTagHelper( ICompositeViewEngine viewEngine, - IViewBufferScope viewBufferScope) +#pragma warning disable PUB0001 // Pubternal type in public API + IViewBufferScope viewBufferScope +#pragma warning restore PUB0001 + ) { _viewEngine = viewEngine ?? throw new ArgumentNullException(nameof(viewEngine)); _viewBufferScope = viewBufferScope ?? throw new ArgumentNullException(nameof(viewBufferScope)); diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs index 8faf17807f..271aa289d8 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs @@ -168,7 +168,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers protected JavaScriptEncoder JavaScriptEncoder { get; } // Internal for ease of use when testing. +#pragma warning disable PUB0001 // Pubternal type in public API protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; } +#pragma warning restore PUB0001 // Shared writer for determining the string content of a TagHelperAttribute's Value. private StringWriter StringWriter diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs index b321a3c44a..ee04bc53b6 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents /// /// The can provide the current instance of /// to a public property of a view component marked - /// with . + /// with . /// public class DefaultViewComponentActivator : IViewComponentActivator { @@ -25,7 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents /// /// The used to create new view component instances. /// +#pragma warning disable PUB0001 // Pubternal type in public API public DefaultViewComponentActivator(ITypeActivatorCache typeActivatorCache) +#pragma warning restore PUB0001 { if (typeActivatorCache == null) { @@ -44,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents } var componentType = context.ViewComponentDescriptor.TypeInfo; - + if (componentType == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs index 0743508566..ad979c9ae0 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs @@ -41,7 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents HtmlEncoder htmlEncoder, IViewComponentSelector selector, IViewComponentInvokerFactory invokerFactory, - IViewBufferScope viewBufferScope) +#pragma warning disable PUB0001 // Pubternal type in public API + IViewBufferScope viewBufferScope +#pragma warning restore PUB0001 + ) { if (descriptorProvider == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs index d2a8614bda..9111ccef3b 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs @@ -32,7 +32,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents /// The . public DefaultViewComponentInvoker( IViewComponentFactory viewComponentFactory, +#pragma warning disable PUB0001 // Pubternal type in public API ViewComponentInvokerCache viewComponentInvokerCache, +#pragma warning restore PUB0001 DiagnosticSource diagnosticSource, ILogger logger) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs index 1dd5d172e6..3d67017743 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs @@ -17,7 +17,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewComponents public DefaultViewComponentInvokerFactory( IViewComponentFactory viewComponentFactory, +#pragma warning disable PUB0001 // Pubternal type in public API ViewComponentInvokerCache viewComponentInvokerCache, +#pragma warning restore PUB0001 DiagnosticSource diagnosticSource, ILoggerFactory loggerFactory) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs index 7fe4a5bc70..02b04d8e53 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs @@ -30,7 +30,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public DefaultValidationHtmlAttributeProvider( IOptions optionsAccessor, IModelMetadataProvider metadataProvider, - ClientValidatorCache clientValidatorCache) +#pragma warning disable PUB0001 // Pubternal type in public API + ClientValidatorCache clientValidatorCache +#pragma warning restore PUB0001 + ) { if (optionsAccessor == null) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs index 8abb93d274..108ac5d7bf 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs @@ -43,7 +43,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, +#pragma warning disable PUB0001 // Pubternal type in public API IViewBufferScope bufferScope, +#pragma warning restore PUB0001 HtmlEncoder htmlEncoder, UrlEncoder urlEncoder) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs index 833a271740..f4bb418c20 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs @@ -24,10 +24,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures IHtmlGenerator htmlGenerator, ICompositeViewEngine viewEngine, IModelMetadataProvider metadataProvider, +#pragma warning disable PUB0001 // Pubternal type in public API IViewBufferScope bufferScope, +#pragma warning restore PUB0001 HtmlEncoder htmlEncoder, UrlEncoder urlEncoder, - ExpressionTextCache expressionTextCache) +#pragma warning disable PUB0001 // Pubternal type in public API + ExpressionTextCache expressionTextCache +#pragma warning restore PUB0001 + ) : base( htmlGenerator, viewEngine, diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs index 46c8830068..f6880a66f7 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs @@ -23,7 +23,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures /// The . public ModelExpressionProvider( IModelMetadataProvider modelMetadataProvider, - ExpressionTextCache expressionTextCache) +#pragma warning disable PUB0001 // Pubternal type in public API + ExpressionTextCache expressionTextCache +#pragma warning restore PUB0001 + ) { if (modelMetadataProvider == null) { From 624a5ed522e8e32a97fb1ba61e25d2302a5abfed Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 15 May 2018 14:41:02 -0700 Subject: [PATCH 026/316] Fix ActionMethodExecutor incorrectly setting DeclaredType on ObjectResult Fixes #7782 --- .../Internal/ActionMethodExecutor.cs | 2 +- .../Internal/ActionMethodExecutorTest.cs | 6 ++ .../XmlOutputFormatterTests.cs | 86 ++++++++++++++++++- .../DataContractSerializerController.cs | 17 ++++ .../Controllers/XmlSerializerController.cs | 13 +++ 5 files changed, 122 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs index 92b6335887..28541b3e55 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs @@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Async method returning awaitable-of-nonvoid var returnValue = await executor.ExecuteAsync(controller, arguments); - var actionResult = ConvertToActionResult(mapper, returnValue, executor.MethodReturnType); + var actionResult = ConvertToActionResult(mapper, returnValue, executor.AsyncResultType); return actionResult; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs index b79a948e7c..6cd343dcd6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs @@ -79,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(TestModel), result.DeclaredType); } [Fact] @@ -97,6 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(TestModel), result.DeclaredType); } [Fact] @@ -115,6 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(object), result.DeclaredType); } [Fact] @@ -199,6 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(TestModel), result.DeclaredType); } [Fact] @@ -217,6 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(object), result.DeclaredType); } [Fact] @@ -251,6 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var result = Assert.IsType(valueTask.Result); Assert.NotNull(result.Value); Assert.IsType(result.Value); + Assert.Equal(typeof(TestModel), result.DeclaredType); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs index fb4102d2c0..35e406ac6a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs @@ -145,5 +145,89 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.NotAcceptable, response.StatusCode); } + + // Xml-based formatters are sensitive to ObjectResult.DeclaredType of the result. A couple of + // tests to verify we don't regress these. + [Fact] + public async Task XmlSerializerFormatter_WorksForActionsReturningTaskOfDummyClass() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/XmlSerializer/GetTaskOfDummyClass"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml")); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + XmlAssert.Equal( + "10", + await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task XmlSerializerFormatter_WorksForActionsReturningDummyClassAsTaskOfObject() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/XmlSerializer/GetTaskOfDummyClassAsObject"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml")); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + XmlAssert.Equal( + "10", + await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task XmlSerializerOutputFormatter_WorksForActionsReturningTaskOfPerson() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/DataContractSerializer/GetTaskOfPerson?name=HelloWorld"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml")); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + XmlAssert.Equal( + "" + + "HelloWorld", + await response.Content.ReadAsStringAsync()); + } + + [Fact] + public async Task XmlSerializerOutputFormatter_WorksForActionsReturningPersonAsTaskOfObject() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/DataContractSerializer/GetTaskOfPersonAsObject?name=HelloWorld"); + request.Headers.Accept.Add(MediaTypeWithQualityHeaderValue.Parse("application/xml")); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + XmlAssert.Equal( + "" + + "HelloWorld", + await response.Content.ReadAsStringAsync()); + } } -} \ No newline at end of file +} diff --git a/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs b/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs index 1a04d327b2..0eaab3d11b 100644 --- a/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs @@ -1,6 +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 System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; @@ -31,5 +32,21 @@ namespace FormatterWebSite // DataContractSerializer should pick up this output. return new Person(name); } + + [HttpPost] + public Task GetTaskOfPerson(string name) + { + // The XmlSerializer should skip and the + // DataContractSerializer should pick up this output. + return Task.FromResult(new Person(name)); + } + + [HttpPost] + public Task GetTaskOfPersonAsObject(string name) + { + // The XmlSerializer should skip and the + // DataContractSerializer should pick up this output. + return Task.FromResult(new Person(name)); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs b/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs index beef850e73..0fffe773ee 100644 --- a/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; @@ -45,5 +46,17 @@ namespace FormatterWebSite { "Hello", "World" } }; } + + [HttpPost] + public Task GetTaskOfDummyClass() + { + return Task.FromResult(new DummyClass { SampleInt = 10 }); + } + + [HttpPost] + public Task GetTaskOfDummyClassAsObject() + { + return Task.FromResult(new DummyClass { SampleInt = 10 }); + } } } \ No newline at end of file From e1eaf6a6e0ce5a97d00b7356627e5bdaec6e1d75 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 9 May 2018 21:56:17 -0700 Subject: [PATCH 027/316] Cleanup TestCommon --- Mvc.NoFun.sln | 17 +++++- Mvc.sln | 17 +++++- build/repo.props | 3 +- .../Properties/AssemblyInfo.cs | 3 +- ...ft.AspNetCore.Mvc.Abstractions.Test.csproj | 3 +- .../DefaultApiDescriptionProviderTest.cs | 2 +- ...oft.AspNetCore.Mvc.ApiExplorer.Test.csproj | 6 +- .../ApplicationAssembliesProviderTest.cs | 43 -------------- .../ContentResultTest.cs | 4 +- .../ControllerBaseTest.cs | 1 - .../MvcBuilderExtensionsTest.cs | 2 +- .../FileContentResultTest.cs | 1 - .../FileStreamResultTest.cs | 1 - .../Formatters/FormatFilterTest.cs | 2 - .../Formatters/FormatterMappingsTest.cs | 1 - .../ObjectResultExecutorTest.cs | 4 -- .../Internal/ActionMethodExecutorTest.cs | 14 ++--- ...ApiBehaviorApplicationModelProviderTest.cs | 4 +- ...ControllerActionDescriptorProviderTests.cs | 2 +- .../DefaultApplicationModelProviderTest.cs | 11 +++- .../Internal/ResponseContentTypeHelperTest.cs | 14 ++--- .../Microsoft.AspNetCore.Mvc.Core.Test.csproj | 8 +-- .../PhysicalFileResultTest.cs | 1 - .../ProducesAttributeTests.cs | 1 - .../RedirectToPageResultTest.cs | 8 +-- .../VirtualFileResultTest.cs | 1 - .../CommonFilterTest.cs | 0 .../CommonResourceInvokerTest.cs | 0 .../MediaTypeAssert.cs | 9 +-- ...soft.AspNetCore.Mvc.Core.TestCommon.csproj | 18 ++++++ .../NonSeekableReadableStream.cs | 2 +- .../SimpleValueProvider.cs | 3 +- .../SimpleValueProviderFactory.cs | 0 .../TestClientModelValidatorProvider.cs | 0 .../TestHttpRequestStreamReaderFactory.cs | 0 .../TestHttpResponseStreamWriterFactory.cs | 0 .../TestModelBinderFactory.cs | 0 .../TestModelMetadataProvider.cs | 0 .../TestModelValidatorProvider.cs | 0 .../ValidationAttributeUtil.cs | 0 .../Microsoft.AspNetCore.Mvc.Cors.Test.csproj | 4 +- .../Internal/CompareAttributeAdapterTest.cs | 21 +++---- .../DataAnnotationsMetadataProviderTest.cs | 7 +-- .../FileExtensionsAttributeAdapterTest.cs | 10 ++-- .../Internal/MaxLengthAttributeAdapterTest.cs | 12 ++-- .../Internal/MinLengthAttributeAdapterTest.cs | 10 ++-- .../Internal/ModelValidationResultComparer.cs | 7 +-- .../NumericClientModelValidatorTest.cs | 12 ++-- .../Internal/RangeAttributeAdapterTest.cs | 8 +-- .../Internal/RequiredAttributeAdapterTest.cs | 8 +-- .../StringLengthAttributeAdapterTest.cs | 13 ++--- ...AspNetCore.Mvc.DataAnnotations.Test.csproj | 4 +- .../JsonInputFormatterTest.cs | 1 - .../JsonPatchInputFormatterTest.cs | 1 - ...AspNetCore.Mvc.Formatters.Json.Test.csproj | 6 +- ....AspNetCore.Mvc.Formatters.Xml.Test.csproj | 3 +- ...ataContractSerializerInputFormatterTest.cs | 1 - .../XmlSerializerInputFormatterTest.cs | 1 - .../Infrastructure}/ResourceFile.cs | 4 +- .../InputObjectValidationTests.cs | 4 +- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 11 +--- .../RemoteAttributeValidationTest.cs | 3 +- .../TagHelpersTest.cs | 9 +-- .../AuthorizeFilterIntegrationTest.cs | 3 +- ...oft.AspNetCore.Mvc.IntegrationTests.csproj | 3 +- .../ModelBindingTestHelper.cs | 2 +- ...ft.AspNetCore.Mvc.Localization.Test.csproj | 6 +- .../MvcRazorMvcBuilderExtensionsTest.cs | 13 ++--- .../RazorPagePropertyActivatorTest.cs | 16 +++--- .../Internal/RazorViewCompilerTest.cs | 1 + ...Microsoft.AspNetCore.Mvc.Razor.Test.csproj | 6 +- .../RazorPageCreateModelExpressionTest.cs | 4 +- .../RazorPageTest.cs | 1 - .../RazorViewEngineOptionsTest.cs | 3 +- .../RazorViewEngineTest.cs | 1 + .../TagHelpers/UrlResolutionTagHelperTest.cs | 2 - .../Internal/PageActionInvokerProviderTest.cs | 2 +- ...soft.AspNetCore.Mvc.RazorPages.Test.csproj | 4 +- .../PageModelTest.cs | 1 - .../PageTest.cs | 1 - .../FormTagHelperTest.cs | 1 - .../InputTagHelperTest.cs | 1 - .../LabelTagHelperTest.cs | 1 - .../LinkTagHelperTest.cs | 2 - ...soft.AspNetCore.Mvc.TagHelpers.Test.csproj | 5 +- .../PartialTagHelperTest.cs | 1 - .../ScriptTagHelperTest.cs | 2 - .../SelectTagHelperTest.cs | 1 - .../TagHelperOutputExtensionsTest.cs | 3 +- .../TextAreaTagHelperTest.cs | 1 - .../ValidationSummaryTagHelperTest.cs | 1 - .../ApplicationAssembliesProviderTest.cs | 56 +++++++++++++++++++ .../Microsoft.AspNetCore.Mvc.Test.csproj | 7 ++- .../xunit.runner.json | 3 + ...Microsoft.AspNetCore.Mvc.TestCommon.csproj | 23 -------- .../PlatformNormalizer.cs | 44 --------------- .../SolutionPathUtility.cs | 44 --------------- ...MvcViewFeaturesMvcBuilderExtensionsTest.cs | 56 ++++++++++--------- .../Internal/DefaultDisplayTemplatesTest.cs | 1 - .../Internal/DefaultEditorTemplatesTest.cs | 1 - ...ft.AspNetCore.Mvc.ViewFeatures.Test.csproj | 6 +- .../Rendering/HtmlHelperCheckboxTest.cs | 1 - .../HtmlHelperDisplayExtensionsTest.cs | 1 - .../HtmlHelperDropDownListExtensionsTest.cs | 1 - .../HtmlHelperEditorExtensionsTest.cs | 1 - .../Rendering/HtmlHelperHiddenTest.cs | 1 - .../HtmlHelperLabelExtensionsTest.cs | 1 - .../Rendering/HtmlHelperLinkGenerationTest.cs | 1 - .../HtmlHelperListBoxExtensionsTest.cs | 1 - .../HtmlHelperPartialExtensionsTest.cs | 1 - .../Rendering/HtmlHelperPasswordTest.cs | 1 - .../HtmlHelperRadioButtonExtensionsTest.cs | 1 - .../Rendering/HtmlHelperSelectTest.cs | 1 - .../HtmlHelperTextAreaExtensionsTest.cs | 1 - .../Rendering/HtmlHelperTextAreaTest.cs | 1 - .../HtmlHelperTextBoxExtensionsTest.cs | 1 - .../Rendering/HtmlHelperTextBoxTest.cs | 1 - ...mlHelperValidationMessageExtensionsTest.cs | 1 - .../HtmlHelperValidationSummaryTest.cs | 1 - .../Rendering/TagBuilderTest.cs | 1 - .../ViewComponentResultTest.cs | 3 +- .../ViewFeatures/AntiforgeryExtensionsTest.cs | 1 - .../ViewFeatures/DefaultHtmlGeneratorTest.cs | 1 - .../ViewFeatures/ViewExecutorTest.cs | 2 - .../HtmlContentUtilities.cs | 2 +- .../HtmlGeneratorUtilities.cs | 0 ...oft.AspNetCore.Mvc.Views.TestCommon.csproj | 18 ++++++ .../TestDirectoryContent.cs | 3 +- .../TestDirectoryFileInfo.cs | 4 +- .../TestFileChangeToken.cs | 0 .../TestFileInfo.cs | 3 +- .../TestFileProvider.cs | 4 +- .../TestRazorCompiledItem.cs | 0 .../TestRazorProjectItem.cs | 0 .../TestViewBufferScope.cs | 0 .../VirtualRazorProjectFileSystem.cs | 0 ...AspNetCore.Mvc.WebApiCompatShimTest.csproj | 2 +- 137 files changed, 310 insertions(+), 447 deletions(-) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/CommonFilterTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/CommonResourceInvokerTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/MediaTypeAssert.cs (83%) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/NonSeekableReadableStream.cs (97%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/SimpleValueProvider.cs (96%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/SimpleValueProviderFactory.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestClientModelValidatorProvider.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestHttpRequestStreamReaderFactory.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestHttpResponseStreamWriterFactory.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestModelBinderFactory.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestModelMetadataProvider.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/TestModelValidatorProvider.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Core.TestCommon}/ValidationAttributeUtil.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure}/ResourceFile.cs (98%) create mode 100644 test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json delete mode 100644 test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj delete mode 100644 test/Microsoft.AspNetCore.Mvc.TestCommon/PlatformNormalizer.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.TestCommon/SolutionPathUtility.cs rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/HtmlContentUtilities.cs (94%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/HtmlGeneratorUtilities.cs (100%) create mode 100644 test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestDirectoryContent.cs (92%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestDirectoryFileInfo.cs (86%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestFileChangeToken.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestFileInfo.cs (92%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestFileProvider.cs (97%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestRazorCompiledItem.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestRazorProjectItem.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/TestViewBufferScope.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.TestCommon => Microsoft.AspNetCore.Mvc.Views.TestCommon}/VirtualRazorProjectFileSystem.cs (100%) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 33f4a84d2f..858650218f 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -35,7 +35,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ta EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TagHelpers.Test", "test\Microsoft.AspNetCore.Mvc.TagHelpers.Test\Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj", "{860119ED-3DB1-424D-8D0A-30132A8A7D96}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TestCommon", "test\Microsoft.AspNetCore.Mvc.TestCommon\Microsoft.AspNetCore.Mvc.TestCommon.csproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core.TestCommon", "test\Microsoft.AspNetCore.Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.IntegrationTests", "test\Microsoft.AspNetCore.Mvc.IntegrationTests\Microsoft.AspNetCore.Mvc.IntegrationTests.csproj", "{864FA09D-1E48-403A-A6C8-4F079D2A30F0}" EndProject @@ -112,6 +112,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Test\Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -546,6 +548,18 @@ Global {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|Mixed Platforms.Build.0 = Release|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|x86.ActiveCfg = Release|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Release|x86.Build.0 = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|x86.ActiveCfg = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Debug|x86.Build.0 = Debug|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Any CPU.Build.0 = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.ActiveCfg = Release|Any CPU + {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -588,6 +602,7 @@ Global {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A} diff --git a/Mvc.sln b/Mvc.sln index da45132198..1eb7f54602 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -75,7 +75,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllersFromServicesWebS EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ControllersFromServicesClassLibrary", "test\WebSites\ControllersFromServicesClassLibrary\ControllersFromServicesClassLibrary.csproj", "{551DC89E-2A13-4CF2-83D7-1ADD802443D5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.TestCommon", "test\Microsoft.AspNetCore.Mvc.TestCommon\Microsoft.AspNetCore.Mvc.TestCommon.csproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Core.TestCommon", "test\Microsoft.AspNetCore.Mvc.Core.TestCommon\Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj", "{F504357E-C2E1-4818-BA5C-9A2EAC25FEE5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CorsWebSite", "test\WebSites\CorsWebSite\CorsWebSite.csproj", "{94BA134D-04B3-48AA-BA55-5A4DB8640F2D}" EndProject @@ -170,6 +170,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "test\WebSites\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj", "{17122147-ADFD-41C8-87D9-CCC582CCA8F9}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -892,6 +894,18 @@ Global {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|Mixed Platforms.Build.0 = Release|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|x86.ActiveCfg = Release|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Release|x86.Build.0 = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|Any CPU.Build.0 = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|x86.ActiveCfg = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Debug|x86.Build.0 = Debug|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Any CPU.ActiveCfg = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Any CPU.Build.0 = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.ActiveCfg = Release|Any CPU + {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -961,6 +975,7 @@ Global {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} + {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/build/repo.props b/build/repo.props index 56142440e8..8156b45eaf 100644 --- a/build/repo.props +++ b/build/repo.props @@ -6,7 +6,8 @@ - + + diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs index abaed09181..964f733284 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs @@ -4,7 +4,8 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.Formatters; +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Core.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] [assembly: TypeForwardedTo(typeof(InputFormatterException))] diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj index 1ca6c1dee7..210346c189 100644 --- a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj @@ -5,8 +5,7 @@ - - + diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index d3f0110711..4ceafa6995 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -1744,7 +1744,7 @@ namespace Microsoft.AspNetCore.Mvc.Description { } - public class BaseProducesController : Controller + public class BaseProducesController : ControllerBase { public IActionResult ReturnsActionResult() { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj index 7a812822b1..3debef1228 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj @@ -5,7 +5,9 @@ - - + + + + diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs index 1904003bdd..c3f864b4d7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs @@ -454,49 +454,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts Assert.Equal(new[] { "ControllersAssembly", "MvcSandbox" }, candidates.Select(a => a.Name)); } - // This test verifies DefaultAssemblyPartDiscoveryProvider.ReferenceAssemblies reflects the actual loadable assemblies - // of the libraries that Microsoft.AspNetCore.Mvc depends on. - // If we add or remove dependencies, this test should be changed together. - [Fact] - public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies() - { - // Arrange - var excludeAssemblies = new string[] - { - "Microsoft.AspNetCore.Mvc.Core.Test", - "Microsoft.AspNetCore.Mvc.Razor.Extensions.Reference", - "Microsoft.AspNetCore.Mvc.TestCommon", - "Microsoft.AspNetCore.Mvc.TestDiagnosticListener", - "Microsoft.AspNetCore.Mvc.WebApiCompatShim", - }; - - var additionalAssemblies = new[] - { - // The following assemblies are not reachable from Microsoft.AspNetCore.Mvc - "Microsoft.AspNetCore.All", - "Microsoft.AspNetCore.Mvc.Formatters.Xml", - }; - - var dependencyContextLibraries = DependencyContext.Load(ThisAssembly) - .CompileLibraries - .Where(r => r.Name.StartsWith("Microsoft.AspNetCore.Mvc", StringComparison.OrdinalIgnoreCase) && - !excludeAssemblies.Contains(r.Name, StringComparer.OrdinalIgnoreCase)) - .Select(r => r.Name); - - var expected = dependencyContextLibraries - .Concat(additionalAssemblies) - .Distinct(StringComparer.OrdinalIgnoreCase) - .OrderBy(p => p, StringComparer.OrdinalIgnoreCase); - - // Act - var referenceAssemblies = ApplicationAssembliesProvider - .ReferenceAssemblies - .OrderBy(p => p, StringComparer.OrdinalIgnoreCase); - - // Assert - Assert.Equal(expected, referenceAssemblies, StringComparer.OrdinalIgnoreCase); - } - private class TestApplicationAssembliesProvider : ApplicationAssembliesProvider { public DependencyContext DependencyContext { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs index a9e878cc8f..6fd9433aa0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs @@ -9,8 +9,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; -using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -259,7 +257,7 @@ namespace Microsoft.AspNetCore.Mvc new ActionDescriptor()); } - private static IServiceCollection CreateServices(params ViewComponentDescriptor[] descriptors) + private static IServiceCollection CreateServices() { // An array pool could return a buffer which is greater or equal to the size of the default character // chunk size. Since the tests here depend on a specific character buffer size to test boundary conditions, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs index f993e61803..d46f73d642 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs @@ -15,7 +15,6 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs index 1aa3240f6f..4d1dbaec0d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc // independent. namespace Microsoft.AspNetCore.Mvc.MvcServiceCollectionExtensionsTestControllers { - public class ControllerTypeA : Microsoft.AspNetCore.Mvc.Controller + public class ControllerTypeA : ControllerBase { } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs index e2d0dd087d..378422388b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs @@ -8,7 +8,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs index 081eaf7b81..bfb001a9bc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs index b33698b52a..9610b485b4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs @@ -7,9 +7,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs index d5428abc3f..15252dc783 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Testing; using Microsoft.Net.Http.Headers; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs index 2cf6981202..04013644df 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs @@ -2,18 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs index 6cd343dcd6..8e3f8c37e0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty()); // Assert - Assert.IsType(valueTask.Result); + Assert.IsType(valueTask.Result); } [Fact] @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty()); // Assert - Assert.IsType(valueTask.Result); + Assert.IsType(valueTask.Result); } [Fact] @@ -166,7 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal var valueTask = actionMethodExecutor.Execute(mapper, objectMethodExecutor, controller, Array.Empty()); // Assert - Assert.IsType(valueTask.Result); + Assert.IsType(valueTask.Result); } [Fact] @@ -183,7 +183,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal // Assert await valueTask; - Assert.IsType(valueTask.Result); + Assert.IsType(valueTask.Result); } [Fact] @@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal public IActionResult ReturnIActionResult() => new ContentResult(); - public PartialViewResult ReturnsIActionResultSubType() => new PartialViewResult(); + public ContentResult ReturnsIActionResultSubType() => new ContentResult(); public ActionResult ReturnsActionResultOfT() => new ActionResult(new TestModel()); @@ -309,9 +309,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Internal return Task.CompletedTask; } - public Task ReturnIActionResultAsync() => Task.FromResult((IActionResult)new ViewResult()); + public Task ReturnIActionResultAsync() => Task.FromResult((IActionResult)new StatusCodeResult(201)); - public Task ReturnsIActionResultSubTypeAsync() => Task.FromResult(new ViewResult()); + public Task ReturnsIActionResultSubTypeAsync() => Task.FromResult(new StatusCodeResult(200)); public Task ReturnsModelAsModelAsync() => Task.FromResult(new TestModel()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index 5ce29079a1..b651874be8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -668,13 +668,13 @@ Environment.NewLine + "int b"; [ApiController] [Route("TestApi")] - private class TestApiController : Controller + private class TestApiController : ControllerBase { [HttpGet] public IActionResult TestAction() => null; } - private class SimpleController : Controller + private class SimpleController : ControllerBase { public IActionResult ActionWithoutFilter() => null; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 6fcaadbd27..56b44dc4fa 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -2024,7 +2024,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private class UserController : Controller + private class UserController : ControllerBase { public string GetUser(int id) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs index e052aed8aa..1090cc5868 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs @@ -1243,7 +1243,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } private class DerivedFromControllerAndExplicitIDisposableImplementationController - : Mvc.Controller, IDisposable + : ViewFeaturesController, IDisposable { void IDisposable.Dispose() { @@ -1251,7 +1251,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private class DerivedFromControllerAndHidesBaseDisposeMethodController : Mvc.Controller + private class DerivedFromControllerAndHidesBaseDisposeMethodController : ViewFeaturesController { public new void Dispose() { @@ -1259,6 +1259,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + private class ViewFeaturesController : ControllerBase, IDisposable + { + public virtual void Dispose() + { + } + } + private class BaseClassWithAttributeRoutesController { [Route("A")] diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs index 9e1e31edec..d7898089b8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs @@ -2,8 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Text; -using Microsoft.AspNetCore.Mvc.Formatters; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Net.Http.Headers; using Xunit; @@ -107,14 +105,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal var defaultContentType = "text/default; p1=p1-value; charset=utf-8"; // Act - string resolvedContentType = null; - Encoding resolvedContentTypeEncoding = null; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( contentType?.ToString(), responseContentType, defaultContentType, - out resolvedContentType, - out resolvedContentTypeEncoding); + out var resolvedContentType, + out var resolvedContentTypeEncoding); // Assert Assert.Equal(expectedContentType, resolvedContentType); @@ -128,14 +124,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal var defaultContentType = "text/plain; charset=utf-8"; // Act - string resolvedContentType = null; - Encoding resolvedContentTypeEncoding = null; ResponseContentTypeHelper.ResolveContentTypeAndEncoding( null, expectedContentType, defaultContentType, - out resolvedContentType, - out resolvedContentTypeEncoding); + out var resolvedContentType, + out var resolvedContentTypeEncoding); // Assert Assert.Equal(expectedContentType, resolvedContentType); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj index 25368d8cc0..aef0c4c3c4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj @@ -10,14 +10,12 @@ - - + + + - - - diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs index 69dd1a4550..e6173fd070 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs index 49084a2c91..4c3e9ae7a3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs @@ -8,7 +8,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Primitives; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs index 40026e2130..17321fe0f6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs @@ -7,8 +7,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; @@ -87,11 +85,11 @@ namespace Microsoft.AspNetCore.Mvc RequestServices = CreateServices(), }; - var pageContext = new PageContext + var pageContext = new ActionContext { HttpContext = httpContext, RouteData = new RouteData(), - ActionDescriptor = new CompiledPageActionDescriptor(), + ActionDescriptor = new ActionDescriptor(), }; pageContext.RouteData.Values.Add("page", "/A/Redirecting/Page"); @@ -144,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc RequestServices = CreateServices(), }; - var pageContext = new PageContext + var pageContext = new ActionContext { HttpContext = httpContext, RouteData = new RouteData(), diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs index 21a20fb637..e9aed292ab 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/CommonFilterTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/MediaTypeAssert.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs similarity index 83% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/MediaTypeAssert.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs index bfd7576836..a6d16fc6f7 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/MediaTypeAssert.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs @@ -5,7 +5,7 @@ using Microsoft.Extensions.Primitives; using Microsoft.Net.Http.Headers; using Xunit.Sdk; -namespace Microsoft.AspNetCore.Mvc.TestCommon +namespace Microsoft.AspNetCore.Mvc { public class MediaTypeAssert { @@ -35,11 +35,8 @@ namespace Microsoft.AspNetCore.Mvc.TestCommon throw new EqualException(left.ToString(), right.ToString()); } - MediaTypeHeaderValue leftMediaType = null; - MediaTypeHeaderValue rightMediaType = null; - - if (!MediaTypeHeaderValue.TryParse(left.Value, out leftMediaType) || - !MediaTypeHeaderValue.TryParse(right.Value, out rightMediaType) || + if (!MediaTypeHeaderValue.TryParse(left.Value, out var leftMediaType) || + !MediaTypeHeaderValue.TryParse(right.Value, out var rightMediaType) || !leftMediaType.Equals(rightMediaType)) { throw new EqualException(left.ToString(), right.ToString()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj new file mode 100644 index 0000000000..44d8da1323 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj @@ -0,0 +1,18 @@ + + + + $(StandardTestTfms) + + + + + + + + + + + + + + diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/NonSeekableReadableStream.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs similarity index 97% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/NonSeekableReadableStream.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs index b729dc6b68..b5ccd405ac 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/NonSeekableReadableStream.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs @@ -6,7 +6,7 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -namespace Microsoft.AspNetCore.Mvc.TestCommon +namespace Microsoft.AspNetCore.Mvc { public class NonSeekableReadStream : Stream { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/SimpleValueProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs similarity index 96% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/SimpleValueProvider.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs index a5e4fac206..cf0a95240c 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/SimpleValueProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs @@ -37,8 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public ValueProviderResult GetValue(string key) { - object rawValue; - if (TryGetValue(key, out rawValue)) + if (TryGetValue(key, out var rawValue)) { if (rawValue != null && rawValue.GetType().IsArray) { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/SimpleValueProviderFactory.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/SimpleValueProviderFactory.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestClientModelValidatorProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestClientModelValidatorProvider.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestHttpRequestStreamReaderFactory.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestHttpRequestStreamReaderFactory.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestHttpResponseStreamWriterFactory.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestHttpResponseStreamWriterFactory.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelBinderFactory.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelBinderFactory.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelMetadataProvider.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelValidatorProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestModelValidatorProvider.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/ValidationAttributeUtil.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/ValidationAttributeUtil.cs rename to test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj index f73ee9967c..684e4dfcef 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj @@ -6,10 +6,8 @@ - + - - diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs index 8466b511fb..ab3c4d0b5d 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Localization; @@ -26,16 +26,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var attribute = new CompareAttribute("OtherProperty"); var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); - // Mono issue - https://github.com/aspnet/External/issues/19 - var expectedMessage = PlatformNormalizer.NormalizeContent( - "'MyPropertyDisplayName' and 'OtherPropertyDisplayName' do not match."); + var expectedMessage = "'MyPropertyDisplayName' and 'OtherPropertyDisplayName' do not match."; var actionContext = new ActionContext(); var context = new ClientModelValidationContext( actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); // Act adapter.AddValidation(context); @@ -78,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); // Act adapter.AddValidation(context); @@ -106,15 +104,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var attribute = new CompareAttribute("OtherProperty"); var adapter = new CompareAttributeAdapter(attribute, stringLocalizer: null); - // Mono issue - https://github.com/aspnet/External/issues/19 - var expectedMessage = PlatformNormalizer.NormalizeContent("'MyProperty' and 'OtherProperty' do not match."); + var expectedMessage = "'MyProperty' and 'OtherProperty' do not match."; var actionContext = new ActionContext(); var context = new ClientModelValidationContext( actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); // Act adapter.AddValidation(context); @@ -151,7 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); // Act adapter.AddValidation(context); @@ -191,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); // Act adapter.AddValidation(context); @@ -224,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal actionContext, metadata, metadataProvider, - new AttributeDictionary()); + new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-equalto", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index ac90d1f155..ad36bec14c 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -1315,12 +1315,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal { using (new CultureReplacer(string.Empty, string.Empty)) { - var hashcode = HashCodeCombiner.Start(); - - hashcode.Add(obj.Key.Name); - hashcode.Add(obj.Key.Group); - - return hashcode.CombinedHash; + return obj.Key.GetHashCode(); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs index 0cdb65c847..0bf66c965c 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs @@ -1,9 +1,9 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var expectedErrorMessage = string.Format(attribute.ErrorMessage, nameof(Profile.PhotoFileName), formattedExtensions); var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: null); - var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -82,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var expectedErrorMessage = string.Format(attribute.ErrorMessage, nameof(Profile.PhotoFileName), formattedExtensions); var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: null); - var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation .Returns(new LocalizedString(attribute.ErrorMessage, expectedErrorMessage)); var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); - var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -142,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation attribute.ErrorMessage = "{0} expects only the following extensions: {1}"; var adapter = new FileExtensionsAttributeAdapter(attribute, stringLocalizer: null); - var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(new ActionContext(), metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-fileextensions", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs index 3b08753d71..b20d0a4518 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new MaxLengthAttributeAdapter(attribute, stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-maxlength", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs index 56d6d42efe..a559e00e3c 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new MinLengthAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = "Array must have at least 2 items."; var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new MinLengthAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-minlength", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs index 289f30ec35..aafd2d3e60 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.Extensions.Internal; namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal { @@ -34,11 +33,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal throw new ArgumentNullException(nameof(obj)); } - var hashCodeCombiner = HashCodeCombiner.Start(); - hashCodeCombiner.Add(obj.MemberName, StringComparer.Ordinal); - hashCodeCombiner.Add(obj.Message, StringComparer.Ordinal); - - return hashCodeCombiner.CombinedHash; + return obj.MemberName.GetHashCode(); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs index 71e0b6adfb..581ccbf672 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Xunit; @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new NumericClientModelValidator(); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); var expectedMessage = "The field DisplayId must be a number."; @@ -57,7 +57,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new NumericClientModelValidator(); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new NumericClientModelValidator(); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -115,7 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new NumericClientModelValidator(); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new NumericClientModelValidator(); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-number", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs index bbf5cf076b..626e915d0c 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs @@ -1,9 +1,9 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var adapter = new RangeAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var adapter = new RangeAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var adapter = new RangeAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-range", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs index 6dec6abfa7..3ea14c9586 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new RequiredAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new RequiredAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new RequiredAttributeAdapter(attribute, stringLocalizer: null); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-required", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs index 87c820dfe8..fdf8bf9186 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs @@ -1,10 +1,10 @@ // 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.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Localization; using Moq; @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var adapter = new StringLengthAttributeAdapter(attribute, stringLocalizer: stringLocalizer.Object); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -47,7 +47,6 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal kvp => { Assert.Equal("data-val", kvp.Key); Assert.Equal("true", kvp.Value); }, kvp => { Assert.Equal("data-val-length", kvp.Key); Assert.Equal(expectedMessage, kvp.Value); }, kvp => { Assert.Equal("data-val-length-max", kvp.Key); Assert.Equal("8", kvp.Value); }); - } [Fact] @@ -64,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -91,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -119,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); // Act adapter.AddValidation(context); @@ -145,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var expectedMessage = attribute.FormatErrorMessage("Length"); var actionContext = new ActionContext(); - var context = new ClientModelValidationContext(actionContext, metadata, provider, new AttributeDictionary()); + var context = new ClientModelValidationContext(actionContext, metadata, provider, new Dictionary()); context.Attributes.Add("data-val", "original"); context.Attributes.Add("data-val-length", "original"); diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj index 84e189a38e..b6673fbd95 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj @@ -5,10 +5,8 @@ - + - - diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs index 0441961c2a..d17e4748fa 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs @@ -11,7 +11,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs index dc097cd5c4..16961e2866 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj index 4ace996ce7..4b93e7b0a3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj @@ -5,11 +5,9 @@ - + + - - - diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj index a02cf7ca47..b6673fbd95 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj @@ -5,9 +5,8 @@ - + - diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 5edf2998e6..885e231a5a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -11,7 +11,6 @@ using System.Xml; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs index 84360e3c94..423edaaa20 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs @@ -12,7 +12,6 @@ using System.Xml.Serialization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/ResourceFile.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs similarity index 98% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/ResourceFile.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs index f1128e0f66..97c51a0d6e 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/ResourceFile.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs @@ -7,6 +7,7 @@ using System.IO; using System.Reflection; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Mvc @@ -190,7 +191,8 @@ namespace Microsoft.AspNetCore.Mvc { // The build system compiles every file under the resources folder as a resource available at runtime // with the same name as the file name. Need to update this file on disc. - var projectPath = SolutionPathUtility.GetProjectPath("test", assembly); + var solutionPath = TestPathUtilities.GetSolutionRootDirectory("Mvc"); + var projectPath = Path.Combine(solutionPath, "test", assembly.GetName().Name); var fullPath = Path.Combine(projectPath, resourceName); WriteFile(fullPath, content); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs index dbe25c6c92..6a038fe5a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -91,12 +91,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); // Mono issue - https://github.com/aspnet/External/issues/29 - Assert.Equal(PlatformNormalizer.NormalizeContent( + Assert.Equal( "The field Id must be between 1 and 2000.," + "The field Name must be a string or array type with a minimum length of '5'.," + "The field Alias must be a string with a minimum length of 3 and a maximum length of 15.," + "The field Designation must match the regular expression " + - (TestPlatformHelper.IsMono ? "[0-9a-zA-Z]*." : "'[0-9a-zA-Z]*'.")), + "'[0-9a-zA-Z]*'.", await response.Content.ReadAsStringAsync()); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 813e61ff85..d9bafa174e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -1,13 +1,11 @@ - + $(StandardTestTfms) - $(DefineConstants);GENERATE_BASELINES $(DefineConstants);__RemoveThisBitTo__GENERATE_BASELINES - $(DefineConstants);FUNCTIONAL_TESTS @@ -26,6 +24,8 @@ + + @@ -35,8 +35,6 @@ - - @@ -51,10 +49,7 @@ - - - diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs index 5e1919c462..8d5064bd49 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs @@ -45,9 +45,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests #if GENERATE_BASELINES ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); #else - // Mono issue - https://github.com/aspnet/External/issues/19 Assert.Equal( - PlatformNormalizer.NormalizeContent(expectedContent), + expectedContent, responseContent, ignoreLineEndingDifferences: true); #endif diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs index b3c4d051aa..9c807942ea 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs @@ -116,9 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); #else expectedContent = string.Format(expectedContent, forgeryToken); - // Mono issue - https://github.com/aspnet/External/issues/19 Assert.Equal( - PlatformNormalizer.NormalizeContent(expectedContent.Trim()), + expectedContent.Trim(), responseContent, ignoreLineEndingDifferences: true); #endif @@ -217,9 +216,8 @@ page:root-content" #if GENERATE_BASELINES ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); #else - // Mono issue - https://github.com/aspnet/External/issues/19 Assert.Equal( - PlatformNormalizer.NormalizeContent(expectedContent), + expectedContent, responseContent, ignoreLineEndingDifferences: true); #endif @@ -285,9 +283,8 @@ page:root-content" #if GENERATE_BASELINES ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); #else - // Mono issue - https://github.com/aspnet/External/issues/19 Assert.Equal( - PlatformNormalizer.NormalizeContent(expectedContent), + expectedContent, responseContent, ignoreLineEndingDifferences: true); #endif diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs index f19a67d45b..1134aa2f73 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; using Xunit; @@ -78,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests serviceCollection.AddAuthorization(); serviceCollection.AddMvc(); serviceCollection - .AddTransient() + .AddSingleton(NullLoggerFactory.Instance) .AddTransient, Logger>() .AddSingleton(); return serviceCollection.BuildServiceProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj index 8e52e2a34f..875d606d3e 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj @@ -6,9 +6,8 @@ - + - diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs index 6aa9da7cf0..afd6b0ca40 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); serviceCollection .AddSingleton() - .AddTransient() + .AddSingleton(NullLoggerFactory.Instance) .AddTransient, Logger>(); if (updateOptions != null) diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj index 1ca6c1dee7..faf5a9a84a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj @@ -5,10 +5,8 @@ - - - - + + diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs index eecee7664c..858de6a312 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs @@ -1,13 +1,11 @@ // 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 Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; -using Microsoft.AspNetCore.Razor.Runtime.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -21,12 +19,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection { // Arrange var services = new ServiceCollection(); - var builder = services - .AddMvc() - .ConfigureApplicationPartManager(manager => - { - manager.ApplicationParts.Add(new TestApplicationPart()); - }); + + var manager = new ApplicationPartManager(); + manager.ApplicationParts.Add(new TestApplicationPart()); + + var builder = new MvcBuilder(services, manager); // Act builder.AddTagHelpersAsServices(); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs index c0221fc68d..c1c68a7d8d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var activator = new RazorPagePropertyActivator( typeof(TestPage), typeof(TestModel), - new TestModelMetadataProvider(), + new EmptyModelMetadataProvider(), propertyValueAccessors: null); var viewContext = new ViewContext(); @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: null, - metadataProvider: new TestModelMetadataProvider(), + metadataProvider: new EmptyModelMetadataProvider(), propertyValueAccessors: null); var viewContext = new ViewContext(); @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenContextInstanceIsNonGeneric() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: typeof(TestModel), @@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_UsesDeclaredTypeOverModelType_WhenCreatingTheViewDataDictionary() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: typeof(TestModel), @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenModelTypeDoesNotMatch() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: typeof(TestModel), @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_CreatesNestedViewDataDictionary_WhenNullModelTypeDoesNotMatch() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: null, @@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_ReturnsInstanceOnContext_IfModelTypeMatches() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: typeof(TestModel), @@ -195,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal public void CreateViewDataDictionary_ReturnsInstanceOnContext_WithNullModelType() { // Arrange - var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadataProvider = new EmptyModelMetadataProvider(); var activator = new RazorPagePropertyActivator( typeof(TestPage), declaredModelType: null, diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs index 929bf63b93..21840806ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Emit; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj index 88f06a4cbb..2cee7fa108 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj @@ -11,13 +11,11 @@ - - + + - - diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs index b16c2fe500..015742b0c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs @@ -209,7 +209,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor private static IModelExpressionProvider CreateModelExpressionProvider() { - var provider = new TestModelMetadataProvider(); + var provider = new EmptyModelMetadataProvider(); var modelExpressionProvider = new ModelExpressionProvider( provider, new ExpressionTextCache()); @@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor private static ViewContext CreateViewContext() { - var provider = new TestModelMetadataProvider(); + var provider = new EmptyModelMetadataProvider(); var viewData = new ViewDataDictionary(provider, new ModelStateDictionary()); var serviceCollection = new ServiceCollection(); serviceCollection.AddSingleton(provider); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs index 0484a805eb..2d7ba2df96 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs index 6172036b6c..30197bae76 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Options; using Xunit; @@ -94,4 +95,4 @@ namespace Microsoft.AspNetCore.Mvc.Razor Assert.Same(fileProvider, accessor.Value.FileProviders[0]); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs index 378ca51d42..be418aa7e3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs index 84db2e9a8e..0eab7b1331 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs @@ -7,9 +7,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.WebEncoders.Testing; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index b2d6aa0186..955149bf03 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -15,12 +15,12 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Compilation; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj index 26a80f2c7a..64835957ec 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj @@ -6,12 +6,10 @@ - + - - diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index b73abd57b9..b3443d36c1 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs index f253c06d43..60d9cc7884 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Routing; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs index 11fbbe5d0a..903a335451 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs index 79e44651b3..ce787ccf14 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs @@ -9,7 +9,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; using Microsoft.AspNetCore.Razor.TagHelpers; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs index 3d4e171a6f..106af2e50c 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index 1c18cf70cf..ed843fe9e6 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -12,11 +12,9 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj index d9c1634c3c..b9c56ca6de 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj @@ -6,12 +6,9 @@ - + - - - diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs index d1f0e16ee1..21ac445239 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index 7abb469556..97a4214606 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -12,11 +12,9 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs index dd41bb306b..8b8f842669 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Testing; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs index 2c1cc87c07..8491d2c142 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs @@ -5,13 +5,12 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Razor.TagHelpers.Testing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.WebEncoders.Testing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs index 614ceb985a..98f32a8050 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Testing; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index 1b4ec3cc80..fec670ba58 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs new file mode 100644 index 0000000000..9213836156 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Extensions.DependencyModel; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationParts +{ + public class ApplicationAssembliesProviderTest + { + private static readonly Assembly ThisAssembly = typeof(ApplicationAssembliesProviderTest).Assembly; + + // This test verifies ApplicationAssembliesProviderTest.ReferenceAssemblies reflects the actual loadable assemblies + // of the libraries that Microsoft.AspNetCore.Mvc depends on. + // If we add or remove dependencies, this test should be changed together. + [Fact] + public void ReferenceAssemblies_ReturnsLoadableReferenceAssemblies() + { + // Arrange + var excludeAssemblies = new string[] + { + "Microsoft.AspNetCore.Mvc.Test", + "Microsoft.AspNetCore.Mvc.Core.TestCommon", + }; + + var additionalAssemblies = new[] + { + // The following assemblies are not reachable from Microsoft.AspNetCore.Mvc + "Microsoft.AspNetCore.All", + "Microsoft.AspNetCore.Mvc.Formatters.Xml", + }; + + var dependencyContextLibraries = DependencyContext.Load(ThisAssembly) + .CompileLibraries + .Where(r => r.Name.StartsWith("Microsoft.AspNetCore.Mvc", StringComparison.OrdinalIgnoreCase) && + !excludeAssemblies.Contains(r.Name, StringComparer.OrdinalIgnoreCase)) + .Select(r => r.Name); + + var expected = dependencyContextLibraries + .Concat(additionalAssemblies) + .Distinct(StringComparer.OrdinalIgnoreCase) + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase); + + // Act + var referenceAssemblies = ApplicationAssembliesProvider + .ReferenceAssemblies + .OrderBy(p => p, StringComparer.OrdinalIgnoreCase); + + // Assert + Assert.Equal(expected, referenceAssemblies, StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj index 90beaff56b..6e674cf924 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj @@ -2,14 +2,17 @@ $(StandardTestTfms) + true + + + + - - diff --git a/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json new file mode 100644 index 0000000000..1c72a421ad --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json @@ -0,0 +1,3 @@ +{ + "shadowCopy": false +} diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj deleted file mode 100644 index 203c301faa..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/Microsoft.AspNetCore.Mvc.TestCommon.csproj +++ /dev/null @@ -1,23 +0,0 @@ - - - - $(StandardTestTfms) - - - - - - - - - - - - - - - - - - - diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/PlatformNormalizer.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/PlatformNormalizer.cs deleted file mode 100644 index 3c8737c9b9..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/PlatformNormalizer.cs +++ /dev/null @@ -1,44 +0,0 @@ -// 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.Text.RegularExpressions; -using Microsoft.AspNetCore.Testing; - -namespace Microsoft.AspNetCore.Mvc -{ - public static class PlatformNormalizer - { - // Mono issue - https://github.com/aspnet/External/issues/19 - public static string NormalizeContent(string input) - { - if (TestPlatformHelper.IsMono) - { - var equivalents = new Dictionary { - { - "The [0-9a-zA-Z ]+ field is required.", "RequiredAttribute_ValidationError" - }, - { - "'[0-9a-zA-Z ]+' and '[0-9a-zA-Z ]+' do not match.", "CompareAttribute_MustMatch" - }, - { - "The field [0-9a-zA-Z ]+ must be a string with a minimum length of [0-9]+ and a " + - "maximum length of [0-9]+.", - "StringLengthAttribute_ValidationErrorIncludingMinimum" - }, - }; - - var result = input; - - foreach (var kvp in equivalents) - { - result = Regex.Replace(result, kvp.Key, kvp.Value); - } - - return result; - } - - return input; - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/SolutionPathUtility.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/SolutionPathUtility.cs deleted file mode 100644 index a3d372fbf2..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/SolutionPathUtility.cs +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Reflection; - -namespace Microsoft.AspNetCore.Mvc -{ - public static class SolutionPathUtility - { - private const string SolutionName = "Mvc.sln"; - - /// - /// Gets the full path to the project. - /// - /// - /// The parent directory of the project. - /// e.g. samples, test, or test/Websites - /// - /// The project's assembly. - /// The full path to the project. - public static string GetProjectPath(string solutionRelativePath, Assembly assembly) - { - var projectName = assembly.GetName().Name; - var applicationBasePath = AppContext.BaseDirectory; - - var directoryInfo = new DirectoryInfo(applicationBasePath); - do - { - var solutionFileInfo = new FileInfo(Path.Combine(directoryInfo.FullName, SolutionName)); - if (solutionFileInfo.Exists) - { - return Path.GetFullPath(Path.Combine(directoryInfo.FullName, solutionRelativePath, projectName)); - } - - directoryInfo = directoryInfo.Parent; - } - while (directoryInfo.Parent != null); - - throw new Exception($"Solution root could not be located using application root {applicationBasePath}."); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs index e8c04b1370..862d5e4ffc 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs @@ -20,20 +20,20 @@ namespace Microsoft.Extensions.DependencyInjection public void AddViewComponentsAsServices_ReplacesViewComponentActivator() { // Arrange - var services = new ServiceCollection(); - var builder = services - .AddMvc() - .ConfigureApplicationPartManager(manager => - { - manager.ApplicationParts.Add(new TestApplicationPart()); - manager.FeatureProviders.Add(new ViewComponentFeatureProvider()); - }); + var builder = CreateBuilder(); + + MvcViewFeaturesMvcCoreBuilderExtensions.AddViewServices(builder.Services); + builder.ConfigureApplicationPartManager(manager => + { + manager.ApplicationParts.Add(new TestApplicationPart()); + manager.FeatureProviders.Add(new ViewComponentFeatureProvider()); + }); // Act builder.AddViewComponentsAsServices(); // Assert - var descriptor = Assert.Single(services.ToList(), d => d.ServiceType == typeof(IViewComponentActivator)); + var descriptor = Assert.Single(builder.Services.ToList(), d => d.ServiceType == typeof(IViewComponentActivator)); Assert.Equal(typeof(ServiceBasedViewComponentActivator), descriptor.ImplementationType); } @@ -41,14 +41,13 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProvider_RegistersExpectedTempDataProvider() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(); // Assert - var descriptor = Assert.Single(services, item => item.ServiceType == typeof(ITempDataProvider)); + var descriptor = Assert.Single(builder.Services, item => item.ServiceType == typeof(ITempDataProvider)); Assert.Equal(typeof(CookieTempDataProvider), descriptor.ImplementationType); } @@ -56,15 +55,14 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProvider_DoesNotRegisterOptionsConfiguration() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(); // Assert Assert.DoesNotContain( - services, + builder.Services, item => item.ServiceType == typeof(IConfigureOptions)); } @@ -72,14 +70,13 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProviderWithSetupAction_RegistersExpectedTempDataProvider() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(options => { }); // Assert - var descriptor = Assert.Single(services, item => item.ServiceType == typeof(ITempDataProvider)); + var descriptor = Assert.Single(builder.Services, item => item.ServiceType == typeof(ITempDataProvider)); Assert.Equal(typeof(CookieTempDataProvider), descriptor.ImplementationType); } @@ -87,15 +84,14 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProviderWithSetupAction_RegistersOptionsConfiguration() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(options => { }); // Assert Assert.Single( - services, + builder.Services, item => item.ServiceType == typeof(IConfigureOptions)); } @@ -103,15 +99,14 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProvider_RegistersExpectedTempDataProvider_IfCalledTwice() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(); builder.AddCookieTempDataProvider(); // Assert - var descriptor = Assert.Single(services, item => item.ServiceType == typeof(ITempDataProvider)); + var descriptor = Assert.Single(builder.Services, item => item.ServiceType == typeof(ITempDataProvider)); Assert.Equal(typeof(CookieTempDataProvider), descriptor.ImplementationType); } @@ -119,15 +114,14 @@ namespace Microsoft.Extensions.DependencyInjection public void AddCookieTempDataProviderWithSetupAction_RegistersExpectedTempDataProvider_IfCalledTwice() { // Arrange - var services = new ServiceCollection(); - var builder = services.AddMvc(); + var builder = CreateBuilder(); // Act builder.AddCookieTempDataProvider(options => { }); builder.AddCookieTempDataProvider(options => { }); // Assert - var descriptor = Assert.Single(services, item => item.ServiceType == typeof(ITempDataProvider)); + var descriptor = Assert.Single(builder.Services, item => item.ServiceType == typeof(ITempDataProvider)); Assert.Equal(typeof(CookieTempDataProvider), descriptor.ImplementationType); } @@ -166,6 +160,14 @@ namespace Microsoft.Extensions.DependencyInjection Assert.Equal(ServiceLifetime.Singleton, collection[2].Lifetime); } + private static MvcBuilder CreateBuilder() + { + var services = new ServiceCollection(); + var manager = new ApplicationPartManager(); + var builder = new MvcBuilder(services, manager); + return builder; + } + public class ConventionsViewComponent { public string Invoke() => "Hello world"; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs index bf9c3446ca..73e3b0cc08 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs @@ -10,7 +10,6 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs index 793f7a176e..30f7f4d0da 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs @@ -14,7 +14,6 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Testing; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj index f3dea58d26..eb45ca869d 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj @@ -5,11 +5,7 @@ - - + - - - diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs index 6f56ab6dde..67cfb7f64e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs index c32e06c3bf..c53a3eaf56 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.Extensions.Localization; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs index b7d1846b0b..a465269d9b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs index a73fc44a2f..9a8365805f 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs @@ -3,7 +3,6 @@ using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs index 2829b1746d..738a0fe051 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs index 95c8d3a38a..1c1a1861e6 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs @@ -4,7 +4,6 @@ using System; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs index 3339241361..be91f77692 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Extensions.Internal; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs index d5277ae2bd..1a2c4d6d4b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs index 23944cc16e..f795adcf2b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs index f951e13c9f..61465fe48e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs index ce3b3ca7fe..cbfb61c7bf 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs index 9a0d279ffe..56ddb2c7a2 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs @@ -8,7 +8,6 @@ using System.Globalization; using System.Linq; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs index 2f30e0f551..b5bd004a48 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs @@ -4,7 +4,6 @@ using System; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs index 1e5d51fc1c..a125cc250d 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; -using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.Rendering diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs index 700f9fbf4c..64444999a6 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs index 82b5710fde..16d0b0e2f0 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq.Expressions; -using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.Rendering diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs index b9e1f5fc4f..6bf8c50b65 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.Core diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs index 5531f4da05..28b6ad4c86 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs @@ -7,7 +7,6 @@ using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Xunit; namespace Microsoft.AspNetCore.Mvc.Rendering diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs index ce76a6c786..0132887614 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.IO; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.Extensions.WebEncoders.Testing; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs index 1744a3380b..fcb0c7c92c 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs @@ -12,10 +12,9 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewComponents; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs index d3b57ffb7d..9123aeed3e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs @@ -3,7 +3,6 @@ using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.TestCommon; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs index c8c7198ad9..d4e5477aab 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs @@ -13,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs index 12be821f84..f55d3a91fd 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs @@ -11,10 +11,8 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Rendering; -using Microsoft.AspNetCore.Mvc.TestCommon; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/HtmlContentUtilities.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs similarity index 94% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/HtmlContentUtilities.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs index 0f7c7aca6e..b828223c65 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/HtmlContentUtilities.cs +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs @@ -6,7 +6,7 @@ using System.Text.Encodings.Web; using Microsoft.AspNetCore.Html; using Microsoft.Extensions.WebEncoders.Testing; -namespace Microsoft.AspNetCore.Mvc.TestCommon +namespace Microsoft.AspNetCore.Mvc { public class HtmlContentUtilities { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/HtmlGeneratorUtilities.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/HtmlGeneratorUtilities.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj new file mode 100644 index 0000000000..f098adae0f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj @@ -0,0 +1,18 @@ + + + + $(StandardTestTfms) + + + + + + + + + + + + + + diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryContent.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs similarity index 92% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryContent.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs index 0ddb4f7823..476a59ef2d 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryContent.cs +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs @@ -5,9 +5,8 @@ using System; using System.Collections; using System.Collections.Generic; using System.IO; -using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Mvc.TestCommon +namespace Microsoft.Extensions.FileProviders { public class TestDirectoryContent : IDirectoryContents, IFileInfo { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryFileInfo.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs similarity index 86% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryFileInfo.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs index 4183025503..8d2a05e29f 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestDirectoryFileInfo.cs +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs @@ -3,10 +3,8 @@ using System; using System.IO; -using System.Text; -using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Mvc.Razor +namespace Microsoft.Extensions.FileProviders { public class TestDirectoryFileInfo : IFileInfo { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileChangeToken.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileChangeToken.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileInfo.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs similarity index 92% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileInfo.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs index 79b2f94c4f..9545c36d5e 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileInfo.cs +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs @@ -4,9 +4,8 @@ using System; using System.IO; using System.Text; -using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Mvc.Razor +namespace Microsoft.Extensions.FileProviders { public class TestFileInfo : IFileInfo { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs similarity index 97% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs index 3e3f62a9e5..e54d2c770c 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestFileProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs @@ -4,11 +4,9 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.AspNetCore.Mvc.TestCommon; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; -namespace Microsoft.AspNetCore.Mvc.Razor +namespace Microsoft.Extensions.FileProviders { public class TestFileProvider : IFileProvider { diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorCompiledItem.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorCompiledItem.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorProjectItem.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestRazorProjectItem.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/TestViewBufferScope.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/TestViewBufferScope.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/VirtualRazorProjectFileSystem.cs b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestCommon/VirtualRazorProjectFileSystem.cs rename to test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj index b10e11d8e7..888ee8f077 100644 --- a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj +++ b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj @@ -7,7 +7,7 @@ - + From 330b74f0ab4c8e337ec93a9129fc9f2df7eca135 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 20 May 2018 19:40:46 +0000 Subject: [PATCH 028/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 140 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 6384b06ed1..3e1941a430 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,84 +5,84 @@ 0.9.9 0.10.13 - 2.2.0-a-preview1-aan-16524 - 2.2.0-preview1-17051 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 + 2.2.0-preview1-34255 + 2.2.0-preview1-17060 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 5.2.4 2.8.0 2.8.0 - 2.2.0-preview1-34184 + 2.2.0-preview1-34255 1.7.0 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 2.2.0-preview1-26509-06 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 2.0.0 2.1.0-rc1 2.2.0-preview1-26509-06 - 2.2.0-preview1-34184 - 2.2.0-preview1-34184 + 2.2.0-preview1-34255 + 2.2.0-preview1-34255 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 89629b454c..cf2fff7def 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17051 -commithash:253c3a480063bc3abaa5cde42f6e27b58457ef9b +version:2.2.0-preview1-17060 +commithash:25b4b134d6f8f7b461928f0d495cfc695ccabb5b From 0c795c4f3366086504ef7849ec7dd24f5e508928 Mon Sep 17 00:00:00 2001 From: Nisha Kaushik Date: Mon, 21 May 2018 21:11:29 +0530 Subject: [PATCH 029/316] #7024 Request: ServiceBasedPageModelActivatorProvider --- .../MvcRazorPagesMvcCoreBuilderExtensions.cs | 4 +- .../ServiceBasedPageModelActivatorProvider.cs | 43 +++++ ...viceBasedPageModelActivatorProviderTest.cs | 152 ++++++++++++++++++ 3 files changed, 198 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index 35a6e75a45..fd8e5b055c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -110,9 +110,11 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Singleton()); // Page and Page model creation and activation - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs new file mode 100644 index 0000000000..9ea5c53790 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// that uses type activation to create Razor Page instances. + /// + public class ServiceBasedPageModelActivatorProvider : IPageModelActivatorProvider + { + public Func CreateActivator(CompiledPageActionDescriptor descriptor) + { + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + var modelTypeInfo = descriptor.ModelTypeInfo?.AsType(); + if (modelTypeInfo == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + nameof(descriptor.ModelTypeInfo), + nameof(descriptor)), + nameof(descriptor)); + } + + return context => + { + return context.HttpContext.RequestServices.GetRequiredService(modelTypeInfo); + }; + } + + public Action CreateReleaser(CompiledPageActionDescriptor descriptor) + { + return null; + } + } +} + diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs new file mode 100644 index 0000000000..f1ead9d4bd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs @@ -0,0 +1,152 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Testing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class ServiceBasedPageModelActivatorProviderTest + { + [Fact] + public void CreateActivator_ThrowsIfModelTypeInfoOnActionDescriptorIsNull() + { + // Arrange + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var descriptor = new CompiledPageActionDescriptor(); + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => activatorProvider.CreateActivator(descriptor), + "descriptor", + "The 'ModelTypeInfo' property of 'descriptor' must not be null."); + } + + [Fact] + public void Create_GetsServicesFromServiceProvider() + { + // Arrange + var simpleModel = new DISimpleModel(); + var serviceProvider = new Mock(MockBehavior.Strict); + serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) + .Returns(simpleModel) + .Verifiable(); + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var pageContext = new PageContext + { + HttpContext = new DefaultHttpContext + { + RequestServices = serviceProvider.Object, + }, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act + var activator = activatorProvider.CreateActivator(pageContext.ActionDescriptor); + var instance = activator(pageContext); + + // Assert + Assert.Same(simpleModel, instance); + serviceProvider.Verify(); + } + + [Fact] + public void CreateActivator_CreatesModelInstance() + { + // Arrange + var controller = new DISimpleModel(); + var serviceProvider = new Mock(MockBehavior.Strict); + serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) + .Returns(controller) + .Verifiable(); + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var pageContext = new PageContext + { + HttpContext = new DefaultHttpContext + { + RequestServices = serviceProvider.Object, + }, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act + var activator = activatorProvider.CreateActivator(pageContext.ActionDescriptor); + var model = activator(pageContext); + + // Assert + var simpleModel = Assert.IsType(model); + Assert.NotNull(simpleModel); + } + + [Fact] + public void Create_ThrowsIfModelIsNotRegisteredInServiceProvider() + { + // Arrange + var expected = "No service for type '" + typeof(DISimpleModel) + "' has been registered."; + var model = new DISimpleModel(); + + var httpContext = new DefaultHttpContext + { + RequestServices = Mock.Of() + }; + + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); + var context = new PageContext + { + HttpContext = httpContext, + ActionDescriptor = new CompiledPageActionDescriptor + { + ModelTypeInfo = typeof(DISimpleModel).GetTypeInfo(), + } + }; + + // Act and Assert + var activator = activatorProvider.CreateActivator(context.ActionDescriptor); + var ex = Assert.Throws( + () => activator(context)); + + Assert.Equal(expected, ex.Message); + } + + [Theory] + [InlineData(typeof(SimpleModel))] + [InlineData(typeof(object))] + public void CreateReleaser_ReturnsNullForPageModels(Type pageType) + { + // Arrange + var context = new PageContext(); + var activator = new ServiceBasedPageModelActivatorProvider(); + var actionDescriptor = new CompiledPageActionDescriptor + { + PageTypeInfo = pageType.GetTypeInfo(), + }; + + // Act + var releaser = activator.CreateReleaser(actionDescriptor); + + // Assert + Assert.Null(releaser); + } + + private class SimpleModel + { + } + + private class DISimpleModel : SimpleModel + { + } + } +} From b8e5036e20f6968f816dff4910293db535e2da67 Mon Sep 17 00:00:00 2001 From: Nisha Kaushik Date: Mon, 21 May 2018 21:15:16 +0530 Subject: [PATCH 030/316] #7024 Request: ServiceBasedPageModelActivatorProvider --- .../ServiceBasedPageModelActivatorProviderTest.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs index f1ead9d4bd..73c51ee42f 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs @@ -16,7 +16,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure public void CreateActivator_ThrowsIfModelTypeInfoOnActionDescriptorIsNull() { // Arrange - var activatorProvider = new ServiceBasedPageModelActivatorProvider(); var descriptor = new CompiledPageActionDescriptor(); @@ -36,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) .Returns(simpleModel) .Verifiable(); - + var activatorProvider = new ServiceBasedPageModelActivatorProvider(); var pageContext = new PageContext { @@ -140,13 +139,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure // Assert Assert.Null(releaser); } - + private class SimpleModel { } private class DISimpleModel : SimpleModel { - } + } } } From 077b1d87a9862b6821a518b4ecb50ced480576a3 Mon Sep 17 00:00:00 2001 From: Nisha Kaushik Date: Tue, 22 May 2018 10:19:21 +0530 Subject: [PATCH 031/316] #7024 Request: ServiceBasedPageModelActivatorProvider [Review Changes] --- .../MvcRazorPagesMvcCoreBuilderExtensions.cs | 4 +--- .../ServiceBasedPageModelActivatorProvider.cs | 6 +++--- ...ServiceBasedPageModelActivatorProviderTest.cs | 16 ++++++++-------- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index fd8e5b055c..35a6e75a45 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -110,11 +110,9 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Singleton()); // Page and Page model creation and activation - services.TryAddSingleton(); + services.TryAddSingleton(); services.TryAddSingleton(); - services.TryAddSingleton(); - services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs index 9ea5c53790..262d0cab21 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs @@ -19,8 +19,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure throw new ArgumentNullException(nameof(descriptor)); } - var modelTypeInfo = descriptor.ModelTypeInfo?.AsType(); - if (modelTypeInfo == null) + var modelType = descriptor.ModelTypeInfo?.AsType(); + if (modelType == null) { throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( nameof(descriptor.ModelTypeInfo), @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure return context => { - return context.HttpContext.RequestServices.GetRequiredService(modelTypeInfo); + return context.HttpContext.RequestServices.GetRequiredService(modelType); }; } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs index 73c51ee42f..1d045c8571 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs @@ -31,10 +31,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { // Arrange var simpleModel = new DISimpleModel(); - var serviceProvider = new Mock(MockBehavior.Strict); + var serviceProvider = new Mock(MockBehavior.Strict); serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) - .Returns(simpleModel) - .Verifiable(); + .Returns(simpleModel) + .Verifiable(); var activatorProvider = new ServiceBasedPageModelActivatorProvider(); var pageContext = new PageContext @@ -62,11 +62,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure public void CreateActivator_CreatesModelInstance() { // Arrange - var controller = new DISimpleModel(); + var simpleModel = new DISimpleModel(); var serviceProvider = new Mock(MockBehavior.Strict); serviceProvider.Setup(s => s.GetService(typeof(DISimpleModel))) - .Returns(controller) - .Verifiable(); + .Returns(simpleModel) + .Verifiable(); var activatorProvider = new ServiceBasedPageModelActivatorProvider(); var pageContext = new PageContext @@ -86,8 +86,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure var model = activator(pageContext); // Assert - var simpleModel = Assert.IsType(model); - Assert.NotNull(simpleModel); + var simpleModel2 = Assert.IsType(model); + Assert.NotNull(simpleModel2); } [Fact] From 49c653ed0baf829cb91bb51bb575306c9f1ec0fa Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 21 May 2018 18:14:54 -0700 Subject: [PATCH 032/316] Infer BindingSource.FormFile for IEnumerable Fixes #7770 --- .../Internal/MvcCoreMvcOptionsSetup.cs | 1 + .../DefaultApplicationModelProviderTest.cs | 53 +++++++++++++++++++ .../MvcOptionsSetupTest.cs | 7 +++ 3 files changed, 61 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs index 21bb72eb27..4cb4f9f3bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs @@ -96,6 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFile), BindingSource.FormFile)); modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormCollection), BindingSource.FormFile)); modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IFormFileCollection), BindingSource.FormFile)); + modelMetadataDetailsProviders.Add(new BindingSourceMetadataProvider(typeof(IEnumerable), BindingSource.FormFile)); // Add types to be excluded from Validation modelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(Type))); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs index 1090cc5868..4af3ea19f5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs @@ -211,6 +211,52 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void OnProvidersExecuting_InfersFormFileSourceForTypesAssignableFromIEnumerableOfFormFiles() + { + // Arrange + var builder = new TestApplicationModelProvider( + new MvcOptions { AllowValidatingTopLevelNodes = true }, + TestModelMetadataProvider.CreateDefaultProvider()); + var typeInfo = typeof(ModelBinderController).GetTypeInfo(); + + var context = new ApplicationModelProviderContext(new[] { typeInfo }); + + // Act + builder.OnProvidersExecuting(context); + + // Assert + var controllerModel = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controllerModel.Actions, a => a.ActionMethod.Name == nameof(ModelBinderController.FormFilesSequences)); + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("formFileEnumerable", parameter.ParameterName); + Assert.Equal(BindingSource.FormFile, parameter.BindingInfo.BindingSource); + }, + parameter => + { + Assert.Equal("formFileCollection", parameter.ParameterName); + Assert.Equal(BindingSource.FormFile, parameter.BindingInfo.BindingSource); + }, + parameter => + { + Assert.Equal("formFileIList", parameter.ParameterName); + Assert.Equal(BindingSource.FormFile, parameter.BindingInfo.BindingSource); + }, + parameter => + { + Assert.Equal("formFileList", parameter.ParameterName); + Assert.Equal(BindingSource.FormFile, parameter.BindingInfo.BindingSource); + }, + parameter => + { + Assert.Equal("formFileArray", parameter.ParameterName); + Assert.Equal(BindingSource.FormFile, parameter.BindingInfo.BindingSource); + }); + } + [Fact] public void OnProvidersExecuting_AddsBindingSources_ForActionParameters_ReadFromModelMetadata() { @@ -1625,6 +1671,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal public IActionResult PostAction([FromQuery] string fromQuery, IFormFileCollection formFileCollection, string unbound) => null; + public IActionResult FormFilesSequences( + IEnumerable formFileEnumerable, + ICollection formFileCollection, + IList formFileIList, + List formFileList, + IFormFile[] formFileArray) => null; + public IActionResult PostAction1(Guid guid) => null; public IActionResult PostAction2([FromQuery] Guid fromQuery) => null; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs index eb9f5434a3..e43d50af95 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; @@ -189,6 +190,12 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(BindingSource.FormFile, formFileParameter.BindingSource); }, provider => + { + var formFileParameter = Assert.IsType(provider); + Assert.Equal(typeof(IEnumerable), formFileParameter.Type); + Assert.Equal(BindingSource.FormFile, formFileParameter.BindingSource); + }, + provider => { var excludeFilter = Assert.IsType(provider); Assert.Equal(typeof(Type), excludeFilter.Type); From 418aac57f4aa962c153485ee9a7b08a5e48a3a7e Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 22 May 2018 11:57:34 -0700 Subject: [PATCH 033/316] [Fixes #7609] ApiBehaviorApplicationModelProvider overwrites existing BindingInfo in entirety when inferring binding sources --- .../ApiBehaviorApplicationModelProvider.cs | 7 +- ...ApiBehaviorApplicationModelProviderTest.cs | 332 +++++++++++++++++- .../ApiBehaviorTest.cs | 30 ++ .../Controllers/ContactApiController.cs | 30 ++ 4 files changed, 391 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 57c65a6d32..9d86de29e5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -155,10 +155,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (bindingSource == null) { bindingSource = InferBindingSourceForParameter(parameter); - parameter.BindingInfo = new BindingInfo - { - BindingSource = bindingSource, - }; + + parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo(); + parameter.BindingInfo.BindingSource = bindingSource; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index b651874be8..bb28383a58 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -6,6 +6,7 @@ using System.ComponentModel; using System.Linq; using System.Reflection; using System.Threading; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -457,12 +458,13 @@ Environment.NewLine + "int b"; Assert.Equal(expected, ex.Message); } - [Fact(Skip = "https://github.com/aspnet/Mvc/issues/7609")] - public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringBindingSource() + [Fact] + public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder_AndExplicitName() { // Arrange - var actionName = nameof(ParameterBindingController.ModelBinderAttribute); - var context = GetContext(typeof(ParameterBindingController)); + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderAttributeWithExplicitModelName); + var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); var provider = GetProvider(); // Act @@ -479,6 +481,263 @@ Environment.NewLine + "int b"; Assert.Equal("top", bindingInfo.BinderModelName); } + [Fact] + public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderType); + var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType_AndExplicitModelName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderTypeWithExplicitModelName); + var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); + Assert.Equal("foo", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQuery); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameter_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("top", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnComplexType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal(string.Empty, bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnComplexTypeWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("gps", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRoute); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameter_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Equal("top", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteOnComplexType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Equal(string.Empty, bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteOnComplexTypeWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Equal("gps", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndPropertyFilterProvider() + { + // Arrange + var expectedPredicate = CustomRequestPredicateAndPropertyFilterProviderAttribute.RequestPredicateStatic; + var expectedPropertyFilter = CustomRequestPredicateAndPropertyFilterProviderAttribute.PropertyFilterStatic; + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.ParameterWithRequestPredicateProvider); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Same(expectedPredicate, bindingInfo.RequestPredicate); + Assert.Same(expectedPropertyFilter, bindingInfo.PropertyFilterProvider.PropertyFilter); + Assert.Null(bindingInfo.BinderModelName); + } + [Fact] public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypesWithCancellationToken() { @@ -749,6 +1008,57 @@ Environment.NewLine + "int b"; [HttpGet("parameter-with-model-binder-attribute")] public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromquery")] + public IActionResult FromQuery([FromQuery] int value) => null; + + [HttpGet("parameter-with-fromquery-and-customname")] + public IActionResult FromQueryWithCustomName([FromQuery(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromquery-on-complextype")] + public IActionResult FromQueryOnComplexType([FromQuery] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromquery-on-complextype-and-customname")] + public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromroute")] + public IActionResult FromRoute([FromRoute] int value) => null; + + [HttpGet("parameter-with-fromroute-and-customname")] + public IActionResult FromRouteWithCustomName([FromRoute(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromroute-on-complextype")] + public IActionResult FromRouteOnComplexType([FromRoute] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromroute-on-complextype-and-customname")] + public IActionResult FromRouteOnComplexTypeWithCustomName([FromRoute(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + + [HttpGet] + public IActionResult ParameterWithRequestPredicateProvider([CustomRequestPredicateAndPropertyFilterProvider] int value) => null; + } + + private class CustomRequestPredicateAndPropertyFilterProviderAttribute : Attribute, IRequestPredicateProvider, IPropertyFilterProvider + { + public static Func RequestPredicateStatic => (c) => true; + public static Func PropertyFilterStatic => (c) => true; + + public Func RequestPredicate => RequestPredicateStatic; + + public Func PropertyFilter => PropertyFilterStatic; + } + + [ApiController] + [Route("[controller]/[action]")] + private class ModelBinderOnParameterController + { + [HttpGet] + public IActionResult ModelBinderAttributeWithExplicitModelName([ModelBinder(Name = "top")] int value) => null; + + [HttpGet] + public IActionResult ModelBinderType([ModelBinder(typeof(TestModelBinder))] string name) => null; + + [HttpGet] + public IActionResult ModelBinderTypeWithExplicitModelName([ModelBinder(typeof(TestModelBinder), Name = "foo")] string name) => null; } [ApiController] @@ -837,5 +1147,19 @@ Environment.NewLine + "int b"; [HttpGet("test")] public IActionResult Action([ModelBinder(typeof(object))] Car car) => null; } + + private class GpsCoordinates + { + public long Latitude { get; set; } + public long Longitude { get; set; } + } + + private class TestModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + throw new NotImplementedException(); + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index 3b5d399c2b..b27a549321 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -173,5 +173,35 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Null(result.Name); Assert.Null(result.Email); } + + [Fact] + public async Task ActionsWithApiBehavior_InferModelBinderType() + { + // Arrange + var expected = "From TestModelBinder: Hello!"; + + // Act + var response = await Client.GetAsync("/contact/ActionWithInferredModelBinderType?foo=Hello!"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, result); + } + + [Fact] + public async Task ActionsWithApiBehavior_InferModelBinderTypeWithExplicitModelName() + { + // Arrange + var expected = "From TestModelBinder: Hello!"; + + // Act + var response = await Client.GetAsync("/contact/ActionWithInferredModelBinderTypeWithExplicitModelName?bar=Hello!"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var result = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, result); + } } } diff --git a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs index e4c443954c..77a757f1ff 100644 --- a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs +++ b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs @@ -8,6 +8,7 @@ using System.Threading; using System.Threading.Tasks; using BasicWebSite.Models; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; namespace BasicWebSite { @@ -72,5 +73,34 @@ namespace BasicWebSite { return contact; } + + [HttpGet("[action]")] + public ActionResult ActionWithInferredModelBinderType( + [ModelBinder(typeof(TestModelBinder))] string foo) + { + return foo; + } + + [HttpGet("[action]")] + public ActionResult ActionWithInferredModelBinderTypeWithExplicitModelName( + [ModelBinder(typeof(TestModelBinder), Name = "bar")] string foo) + { + return foo; + } + + private class TestModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName); + if (val == null) + { + return Task.CompletedTask; + } + + bindingContext.Result = ModelBindingResult.Success("From TestModelBinder: " + val); + return Task.CompletedTask; + } + } } } From edf4e8fd9e238409f77c34ebd66d8ed8be332962 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Mon, 21 May 2018 12:09:56 -0700 Subject: [PATCH 034/316] DataAnnotations of Enum values use DataAnnotationLocalizerProvider --- samples/MvcSandbox/Startup.cs | 2 +- .../CompatibilityVersion.cs | 12 ++ .../DataAnnotationsLocalizationServices.cs | 3 + .../DataAnnotationsMetadataProvider.cs | 14 +- ...calizationConfigureCompatibilityOptions.cs | 35 +++++ .../MvcDataAnnotationsLocalizationOptions.cs | 54 ++++++- .../TestModelMetadataProvider.cs | 12 +- .../TestModelValidatorProvider.cs | 12 +- .../DataAnnotationsMetadataProviderTest.cs | 139 +++++++++++++++--- .../DataAnnotationTests.cs | 38 +++++ ...MvcLocalizationMvcBuilderExtensionsTest.cs | 5 + ...ocalizationMvcCoreBuilderExtensionsTest.cs | 5 + .../Internal/DefaultEditorTemplatesTest.cs | 4 + .../Rendering/DefaultTemplatesUtilities.cs | 24 ++- .../Controllers/DataAnnotationController.cs | 16 ++ .../WebSites/RazorWebSite/Models/EnumModel.cs | 20 +++ test/WebSites/RazorWebSite/Program.cs | 26 ++++ .../Resources/Models/ModelEnum.resx | 123 ++++++++++++++++ .../RazorWebSite/Resources/SingleType.resx | 123 ++++++++++++++++ test/WebSites/RazorWebSite/SingleType.cs | 10 ++ test/WebSites/RazorWebSite/Startup.cs | 18 --- .../RazorWebSite/StartupDataAnnotations.cs | 54 +++++++ .../RazorWebSite/Views/Enum/Enum.cshtml | 6 + 23 files changed, 697 insertions(+), 58 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs create mode 100644 test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs create mode 100644 test/WebSites/RazorWebSite/Models/EnumModel.cs create mode 100644 test/WebSites/RazorWebSite/Program.cs create mode 100644 test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx create mode 100644 test/WebSites/RazorWebSite/Resources/SingleType.resx create mode 100644 test/WebSites/RazorWebSite/SingleType.cs create mode 100644 test/WebSites/RazorWebSite/StartupDataAnnotations.cs create mode 100644 test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml diff --git a/samples/MvcSandbox/Startup.cs b/samples/MvcSandbox/Startup.cs index b963a03a6f..0b3360ec8a 100644 --- a/samples/MvcSandbox/Startup.cs +++ b/samples/MvcSandbox/Startup.cs @@ -14,7 +14,7 @@ namespace MvcSandbox // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_1); + services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs index 91372a0d19..0260f464a7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs @@ -59,6 +59,18 @@ namespace Microsoft.AspNetCore.Mvc /// Version_2_1, + /// + /// Sets the default value of settings on to match the behavior of + /// ASP.NET Core MVC 2.2. + /// + /// + /// ASP.NET Core MVC 2.2 introduces compatibility switches for the following: + /// + /// MvcDataAnnotationsLocalizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes + /// + /// + Version_2_2, + /// /// Sets the default value of settings on to match the latest release. Use this /// value with care, upgrading minor versions will cause breaking changes when using . diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs index f54cd9dd5d..3ef1fde707 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs @@ -27,6 +27,9 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal , MvcDataAnnotationsLocalizationOptionsSetup>()); } + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcDataAnnotationsLocalizationConfigureCompatibilityOptions>()); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs index 0248cee5c5..b91e4b9501 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -182,7 +182,19 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var groupedDisplayNamesAndValues = new List>(); var namesAndValues = new Dictionary(); - var enumLocalizer = _stringLocalizerFactory?.Create(underlyingType); + + IStringLocalizer enumLocalizer = null; + if (_localizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes) + { + if (_stringLocalizerFactory != null && _localizationOptions.DataAnnotationLocalizerProvider != null) + { + enumLocalizer = _localizationOptions.DataAnnotationLocalizerProvider(underlyingType, _stringLocalizerFactory); + } + } + else + { + enumLocalizer = _stringLocalizerFactory?.Create(underlyingType); + } var enumFields = Enum.GetNames(underlyingType) .Select(name => underlyingType.GetField(name)) diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..e3eab734b7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + internal class MvcDataAnnotationsLocalizationConfigureCompatibilityOptions : ConfigureCompatibilityOptions + { + public MvcDataAnnotationsLocalizationConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) + { + } + + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(MvcDataAnnotationsLocalizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes)] = true; + } + + return values; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs index 99d8742296..911c7139c6 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Localization; namespace Microsoft.AspNetCore.Mvc.DataAnnotations @@ -9,11 +12,60 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations /// /// Provides programmatic configuration for DataAnnotations localization in the MVC framework. /// - public class MvcDataAnnotationsLocalizationOptions + public class MvcDataAnnotationsLocalizationOptions : IEnumerable { + private readonly CompatibilitySwitch _allowDataAnnotationsLocalizationForEnumDisplayAttributes; + private readonly ICompatibilitySwitch[] _switches; + /// /// The delegate to invoke for creating . /// public Func DataAnnotationLocalizerProvider; + + /// + /// Instantiates a new instance of the class. + /// + public MvcDataAnnotationsLocalizationOptions() + { + _allowDataAnnotationsLocalizationForEnumDisplayAttributes = new CompatibilitySwitch(nameof(AllowDataAnnotationsLocalizationForEnumDisplayAttributes)); + + _switches = new ICompatibilitySwitch[] + { + _allowDataAnnotationsLocalizationForEnumDisplayAttributes + }; + } + + /// + /// Gets or sets a value that determines if should be used while localizing types. + /// If set to true will be used in localizing types. + /// If set to false the localization will search for values in resource files for the . + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take precedence + /// over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or then + /// this setting will have the value false unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value true unless explicitly configured. + /// + /// + public bool AllowDataAnnotationsLocalizationForEnumDisplayAttributes + { + get => _allowDataAnnotationsLocalizationForEnumDisplayAttributes.Value; + set => _allowDataAnnotationsLocalizationForEnumDisplayAttributes.Value = value; + } + + public IEnumerator GetEnumerator() => ((IEnumerable)_switches).GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs index 47f6b7d2ed..faff630e0b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs @@ -16,6 +16,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { public class TestModelMetadataProvider : DefaultModelMetadataProvider { + private static DataAnnotationsMetadataProvider CreateDefaultDataAnnotationsProvider(IStringLocalizerFactory stringLocalizerFactory) + { + var options = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + options.Value.DataAnnotationLocalizerProvider = (modelType, localizerFactory) => localizerFactory.Create(modelType); + + return new DataAnnotationsMetadataProvider(options, stringLocalizerFactory); + } + // Creates a provider with all the defaults - includes data annotations public static ModelMetadataProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null) { @@ -23,9 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { new DefaultBindingMetadataProvider(), new DefaultValidationMetadataProvider(), - new DataAnnotationsMetadataProvider( - Options.Create(new MvcDataAnnotationsLocalizationOptions()), - stringLocalizerFactory), + CreateDefaultDataAnnotationsProvider(stringLocalizerFactory), new DataMemberRequiredBindingMetadataProvider(), }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs index 96d714ee24..230f7970b4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation @@ -12,15 +13,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation public class TestModelValidatorProvider : CompositeModelValidatorProvider { // Creates a provider with all the defaults - includes data annotations - public static CompositeModelValidatorProvider CreateDefaultProvider() + public static CompositeModelValidatorProvider CreateDefaultProvider(IStringLocalizerFactory stringLocalizerFactory = null) { + var options = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + options.Value.DataAnnotationLocalizerProvider = (modelType, localizerFactory) => localizerFactory.Create(modelType); + var providers = new IModelValidatorProvider[] { new DefaultModelValidatorProvider(), new DataAnnotationsModelValidatorProvider( new ValidationAttributeAdapterProvider(), - Options.Create(new MvcDataAnnotationsLocalizationOptions()), - stringLocalizerFactory: null) + options, + stringLocalizerFactory) }; return new TestModelValidatorProvider(providers); @@ -31,4 +35,4 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index ad36bec14c..10dfe11b01 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -10,7 +10,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Internal; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Moq; @@ -18,6 +17,12 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal { + public enum TestEnum + { + [Display(Name = "DisplayNameValue")] + DisplayNameValue + } + public class DataAnnotationsMetadataProviderTest { // Includes attributes with a 'simple' effect on display details. @@ -270,6 +275,94 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal Assert.Equal("DisplayNameAttributeValue", context.DisplayMetadata.DisplayName()); } + [Fact] + public void CreateDisplayMetadata_DisplayNameAttribute_OnEnum_CompatSwitchWorks() + { + // Arrange + var unsharedLocalizer = new Mock(MockBehavior.Strict); + unsharedLocalizer + .Setup(s => s["DisplayNameValue"]) + .Returns(new LocalizedString("DisplaynameValue", "didn't use shared")); + + var sharedLocalizer = new Mock(MockBehavior.Strict); + sharedLocalizer + .Setup(s => s["DisplayNameValue"]) + .Returns(() => new LocalizedString("DisplayNameValue", "used shared")); + + var stringLocalizerFactoryMock = new Mock(MockBehavior.Strict); + stringLocalizerFactoryMock + .Setup(s => s.Create(typeof(TestEnum))) + .Returns(() => unsharedLocalizer.Object); + stringLocalizerFactoryMock + .Setup(s => s.Create(typeof(EmptyClass))) + .Returns(() => sharedLocalizer.Object); + + var localizationOptions = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + localizationOptions.Value.AllowDataAnnotationsLocalizationForEnumDisplayAttributes = false; + localizationOptions.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) => + { + return stringLocalizerFactory.Create(typeof(EmptyClass)); + }; + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: stringLocalizerFactoryMock.Object); + + var displayName = new DisplayNameAttribute("DisplayNameValue"); + + var attributes = new Attribute[] { displayName }; + var key = ModelMetadataIdentity.ForType(typeof(TestEnum)); + var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Collection(context.DisplayMetadata.EnumGroupedDisplayNamesAndValues, + (e) => Assert.Equal("didn't use shared", e.Key.Name)); + } + + [Fact] + public void CreateDisplayMetadata_DisplayNameAttribute_OnEnum_CompatShimOn() + { + // Arrange + var sharedLocalizer = new Mock(MockBehavior.Strict); + sharedLocalizer + .Setup(s => s["DisplayNameValue"]) + .Returns(new LocalizedString("DisplayNameValue", "Name from DisplayNameAttribute")); + + var stringLocalizerFactoryMock = new Mock(MockBehavior.Strict); + stringLocalizerFactoryMock + .Setup(s => s.Create(typeof(EmptyClass))) + .Returns(() => sharedLocalizer.Object); + + var localizationOptions = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + localizationOptions.Value.AllowDataAnnotationsLocalizationForEnumDisplayAttributes = true; + localizationOptions.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) => + { + return stringLocalizerFactory.Create(typeof(EmptyClass)); + }; + + var provider = new DataAnnotationsMetadataProvider( + localizationOptions, + stringLocalizerFactory: stringLocalizerFactoryMock.Object); + + var displayName = new DisplayNameAttribute("DisplayNameValue"); + + var attributes = new Attribute[] { displayName }; + var key = ModelMetadataIdentity.ForType(typeof(TestEnum)); + var context = new DisplayMetadataProviderContext(key, GetModelAttributes(attributes)); + + // Act + provider.CreateDisplayMetadata(context); + + // Assert + Assert.Collection(context.DisplayMetadata.EnumGroupedDisplayNamesAndValues, (e) => + { + Assert.Equal("Name from DisplayNameAttribute", e.Key.Name); + }); + } + [Fact] public void CreateDisplayMetadata_DisplayNameAttribute_LocalizesDisplayName() { @@ -568,16 +661,13 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal stringLocalizerFactoryMock .Setup(f => f.Create(It.IsAny())) .Returns(stringLocalizer.Object); - var options = Options.Create(new MvcDataAnnotationsLocalizationOptions()); options.Value.DataAnnotationLocalizerProvider = (type, stringLocalizerFactory) => { return stringLocalizerFactory.Create(type); }; - var provider = new DataAnnotationsMetadataProvider( - options, - stringLocalizerFactoryMock.Object); + var provider = new DataAnnotationsMetadataProvider(options, stringLocalizerFactoryMock.Object); var display = new DisplayAttribute() { @@ -594,13 +684,13 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal provider.CreateDisplayMetadata(context); // Assert - using (new CultureReplacer("en-US", "en-US")) + using(new CultureReplacer("en-US", "en-US")) { Assert.Equal("name from localizer en-US", context.DisplayMetadata.DisplayName()); Assert.Equal("description from localizer en-US", context.DisplayMetadata.Description()); Assert.Equal("prompt from localizer en-US", context.DisplayMetadata.Placeholder()); } - using (new CultureReplacer("fr-FR", "fr-FR")) + using(new CultureReplacer("fr-FR", "fr-FR")) { Assert.Equal("name from localizer fr-FR", context.DisplayMetadata.DisplayName()); Assert.Equal("description from localizer fr-FR", context.DisplayMetadata.Description()); @@ -840,14 +930,14 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal .Setup(s => s[It.IsAny()]) .Returns((index) => new LocalizedString(index, index + " value")); - var stringLocalizerFactory = new Mock(MockBehavior.Strict); - stringLocalizerFactory + var stringLocalizerFactoryMock = new Mock(MockBehavior.Strict); + stringLocalizerFactoryMock .Setup(f => f.Create(It.IsAny())) .Returns(stringLocalizer.Object); - var provider = new DataAnnotationsMetadataProvider( - Options.Create(new MvcDataAnnotationsLocalizationOptions()), - stringLocalizerFactory.Object); + var options = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + options.Value.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => stringLocalizerFactory.Create(modelType); + var provider = new DataAnnotationsMetadataProvider(options, stringLocalizerFactoryMock.Object); // Act provider.CreateDisplayMetadata(context); @@ -1036,12 +1126,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal // Assert var groupTwo = Assert.Single(enumNameAndGroup, e => e.Value.Equals("2", StringComparison.Ordinal)); - using (new CultureReplacer("en-US", "en-US")) + using(new CultureReplacer("en-US", "en-US")) { Assert.Equal("Loc_Two_Name", groupTwo.Key.Name); } - using (new CultureReplacer("fr-FR", "fr-FR")) + using(new CultureReplacer("fr-FR", "fr-FR")) { Assert.Equal("Loc_Two_Name", groupTwo.Key.Name); } @@ -1056,12 +1146,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal // Assert var groupTwo = Assert.Single(enumNameAndGroup, e => e.Value.Equals("2", StringComparison.Ordinal)); - using (new CultureReplacer("en-US", "en-US")) + using(new CultureReplacer("en-US", "en-US")) { Assert.Equal("Loc_Two_Name en-US", groupTwo.Key.Name); } - using (new CultureReplacer("fr-FR", "fr-FR")) + using(new CultureReplacer("fr-FR", "fr-FR")) { Assert.Equal("Loc_Two_Name fr-FR", groupTwo.Key.Name); } @@ -1076,12 +1166,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal // Assert var groupThree = Assert.Single(enumNameAndGroup, e => e.Value.Equals("3", StringComparison.Ordinal)); - using (new CultureReplacer("en-US", "en-US")) + using(new CultureReplacer("en-US", "en-US")) { Assert.Equal("type three name en-US", groupThree.Key.Name); } - using (new CultureReplacer("fr-FR", "fr-FR")) + using(new CultureReplacer("fr-FR", "fr-FR")) { Assert.Equal("type three name fr-FR", groupThree.Key.Name); } @@ -1096,12 +1186,12 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal var groupThree = Assert.Single(enumNameAndGroup, e => e.Value.Equals("3", StringComparison.Ordinal)); // Assert - using (new CultureReplacer("en-US", "en-US")) + using(new CultureReplacer("en-US", "en-US")) { Assert.Equal("type three name en-US", groupThree.Key.Name); } - using (new CultureReplacer("fr-FR", "fr-FR")) + using(new CultureReplacer("fr-FR", "fr-FR")) { Assert.Equal("type three name fr-FR", groupThree.Key.Name); } @@ -1269,8 +1359,11 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal .Setup(factory => factory.Create(typeof(EnumWithLocalizedDisplayNames))) .Returns(stringLocalizer.Object); + var options = Options.Create(new MvcDataAnnotationsLocalizationOptions()); + options.Value.DataAnnotationLocalizerProvider = (modelType, localizerFactory) => localizerFactory.Create(modelType); + return new DataAnnotationsMetadataProvider( - Options.Create(new MvcDataAnnotationsLocalizationOptions()), + options, useStringLocalizer ? stringLocalizerFactory.Object : null); } @@ -1304,7 +1397,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal public bool Equals(KeyValuePair x, KeyValuePair y) { - using (new CultureReplacer(string.Empty, string.Empty)) + using(new CultureReplacer(string.Empty, string.Empty)) { return x.Key.Name.Equals(y.Key.Name, StringComparison.Ordinal) && x.Key.Group.Equals(y.Key.Group, StringComparison.Ordinal); @@ -1313,7 +1406,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal public int GetHashCode(KeyValuePair obj) { - using (new CultureReplacer(string.Empty, string.Empty)) + using(new CultureReplacer(string.Empty, string.Empty)) { return obj.Key.GetHashCode(); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs new file mode 100644 index 0000000000..a67fb5dd83 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs @@ -0,0 +1,38 @@ +using System.Net; +using System.Threading.Tasks; +using RazorWebSite; +using Microsoft.AspNetCore.Hosting; +using Xunit; +using System.Linq; +using System.Net.Http; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class DataAnnotationTests : IClassFixture> + { + private HttpClient Client { get; set; } + + public DataAnnotationTests(MvcTestFixture fixture) + { + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(builder => + { + builder.UseStartup(); + }); + Client = factory.CreateDefaultClient(); + } + + private const string EnumUrl = "http://localhost/Enum/Enum"; + + [Fact] + public async Task DataAnnotationLocalizionOfEnums_FromDataAnnotationLocalizerProvider() + { + // Arrange & Act + var response = await Client.GetAsync(EnumUrl); + var content = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Contains("FirstOptionDisplay from singletype", content); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs index 1b0b4e947f..8b6fe4bebe 100644 --- a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Xunit; @@ -104,6 +106,9 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Test { // Arrange var builder = new TestMvcBuilder(); + + builder.Services.AddSingleton(NullLoggerFactory.Instance); + var dataAnnotationLocalizerProvider = new Func((type, factory) => { return null; diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs index 1412d35c94..3956d79f17 100644 --- a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs @@ -8,6 +8,8 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Xunit; @@ -104,6 +106,9 @@ namespace Microsoft.AspNetCore.Mvc.Localization.Test { // Arrange var builder = new TestMvcCoreBuilder(); + + builder.Services.AddSingleton(NullLoggerFactory.Instance); + var dataAnnotationLocalizerProvider = new Func((type, factory) => { return null; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs index 30f7f4d0da..a8b6659a91 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs @@ -659,6 +659,7 @@ Environment.NewLine; Mock.Of(), viewEngine.Object, provider, + localizerFactory: null, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; @@ -701,6 +702,7 @@ Environment.NewLine; Mock.Of(), viewEngine.Object, provider, + localizerFactory: null, innerHelper => new StubbyHtmlHelper(innerHelper)); // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. @@ -742,6 +744,7 @@ Environment.NewLine; Mock.Of(), viewEngine.Object, provider, + localizerFactory: null, innerHelper => new StubbyHtmlHelper(innerHelper)); helper.ViewData["Property1"] = "True"; @@ -784,6 +787,7 @@ Environment.NewLine; Mock.Of(), viewEngine.Object, provider, + localizerFactory: null, innerHelper => new StubbyHtmlHelper(innerHelper)); // TemplateBuilder sets FormattedModelValue before calling TemplateRenderer and it's used in most templates. diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs index 75649f3223..24377c95eb 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs @@ -80,6 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering CreateUrlHelper(), CreateViewEngine(), metadataProvider, + localizerFactory: null, innerHelperWrapper: null, htmlGenerator: htmlGenerator, idAttributeDotReplacement: null); @@ -92,6 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering CreateUrlHelper(), CreateViewEngine(), TestModelMetadataProvider.CreateDefaultProvider(), + localizerFactory: null, innerHelperWrapper: null, htmlGenerator: null, idAttributeDotReplacement: null); @@ -106,6 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering CreateUrlHelper(), CreateViewEngine(), TestModelMetadataProvider.CreateDefaultProvider(), + localizerFactory: null, innerHelperWrapper: null, htmlGenerator: null, idAttributeDotReplacement: idAttributeDotReplacement); @@ -127,6 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering CreateUrlHelper(), CreateViewEngine(), provider, + localizerFactory: null, innerHelperWrapper: null, htmlGenerator: null, idAttributeDotReplacement: idAttributeDotReplacement); @@ -167,7 +171,8 @@ namespace Microsoft.AspNetCore.Mvc.Rendering model, CreateUrlHelper(), viewEngine, - TestModelMetadataProvider.CreateDefaultProvider(stringLocalizerFactory)); + TestModelMetadataProvider.CreateDefaultProvider(stringLocalizerFactory), + stringLocalizerFactory); } public static HtmlHelper GetHtmlHelper( @@ -180,6 +185,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering CreateUrlHelper(), viewEngine, TestModelMetadataProvider.CreateDefaultProvider(), + localizerFactory: null, innerHelperWrapper); } @@ -187,9 +193,10 @@ namespace Microsoft.AspNetCore.Mvc.Rendering TModel model, IUrlHelper urlHelper, ICompositeViewEngine viewEngine, - IModelMetadataProvider provider) + IModelMetadataProvider provider, + IStringLocalizerFactory localizerFactory = null) { - return GetHtmlHelper(model, urlHelper, viewEngine, provider, innerHelperWrapper: null); + return GetHtmlHelper(model, urlHelper, viewEngine, provider, localizerFactory, innerHelperWrapper: null); } public static HtmlHelper GetHtmlHelper( @@ -197,6 +204,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering IUrlHelper urlHelper, ICompositeViewEngine viewEngine, IModelMetadataProvider provider, + IStringLocalizerFactory localizerFactory, Func innerHelperWrapper) { var viewData = new ViewDataDictionary(provider); @@ -207,6 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering urlHelper, viewEngine, provider, + localizerFactory, innerHelperWrapper, htmlGenerator: null, idAttributeDotReplacement: null); @@ -217,6 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering IUrlHelper urlHelper, ICompositeViewEngine viewEngine, IModelMetadataProvider provider, + IStringLocalizerFactory localizerFactory, Func innerHelperWrapper, IHtmlGenerator htmlGenerator, string idAttributeDotReplacement) @@ -229,14 +239,14 @@ namespace Microsoft.AspNetCore.Mvc.Rendering { options.HtmlHelperOptions.IdAttributeDotReplacement = idAttributeDotReplacement; } - var localizationOptionsAccesor = new Mock>(); - localizationOptionsAccesor.SetupGet(o => o.Value).Returns(new MvcDataAnnotationsLocalizationOptions()); + var localizationOptions = new MvcDataAnnotationsLocalizationOptions(); + var localizationOptionsAccesor = Options.Create(localizationOptions); options.ClientModelValidatorProviders.Add(new DataAnnotationsClientModelValidatorProvider( new ValidationAttributeAdapterProvider(), - localizationOptionsAccesor.Object, - stringLocalizerFactory: null)); + localizationOptionsAccesor, + localizerFactory)); var urlHelperFactory = new Mock(); urlHelperFactory diff --git a/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs b/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs new file mode 100644 index 0000000000..7d4bdb2c4a --- /dev/null +++ b/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs @@ -0,0 +1,16 @@ +// 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 RazorWebSite.Models; +using Microsoft.AspNetCore.Mvc; + +namespace RazorWebSite.Controllers +{ + public class EnumController : Controller + { + public IActionResult Enum() + { + return View(new EnumModel{ Id = ModelEnum.FirstOption }); + } + } +} diff --git a/test/WebSites/RazorWebSite/Models/EnumModel.cs b/test/WebSites/RazorWebSite/Models/EnumModel.cs new file mode 100644 index 0000000000..3a993cc65a --- /dev/null +++ b/test/WebSites/RazorWebSite/Models/EnumModel.cs @@ -0,0 +1,20 @@ +// 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.ComponentModel.DataAnnotations; + +namespace RazorWebSite.Models +{ + public enum ModelEnum + { + [Display(Name = "FirstOptionDisplay")] + FirstOption, + SecondOptions + } + + public class EnumModel + { + [Display(Name = "ModelEnum")] + public ModelEnum Id { get; set; } + } +} diff --git a/test/WebSites/RazorWebSite/Program.cs b/test/WebSites/RazorWebSite/Program.cs new file mode 100644 index 0000000000..6e8622bb7b --- /dev/null +++ b/test/WebSites/RazorWebSite/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 RazorWebSite +{ + 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/RazorWebSite/Resources/Models/ModelEnum.resx b/test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx new file mode 100644 index 0000000000..af20175466 --- /dev/null +++ b/test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + first option from enum resx + + \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/Resources/SingleType.resx b/test/WebSites/RazorWebSite/Resources/SingleType.resx new file mode 100644 index 0000000000..8fa8c677c9 --- /dev/null +++ b/test/WebSites/RazorWebSite/Resources/SingleType.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + FirstOptionDisplay from singletype + + \ No newline at end of file diff --git a/test/WebSites/RazorWebSite/SingleType.cs b/test/WebSites/RazorWebSite/SingleType.cs new file mode 100644 index 0000000000..ab8ec1e9c6 --- /dev/null +++ b/test/WebSites/RazorWebSite/SingleType.cs @@ -0,0 +1,10 @@ +// 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 RazorWebSite +{ + public class SingleType + { + + } +} diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index 17fee9d6d7..d0a4244558 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -3,10 +3,8 @@ using System.Collections.Generic; using System.Globalization; -using System.IO; using System.Reflection; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Razor.TagHelpers; @@ -72,21 +70,5 @@ namespace RazorWebSite 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(); } } - diff --git a/test/WebSites/RazorWebSite/StartupDataAnnotations.cs b/test/WebSites/RazorWebSite/StartupDataAnnotations.cs new file mode 100644 index 0000000000..7ec35e86fa --- /dev/null +++ b/test/WebSites/RazorWebSite/StartupDataAnnotations.cs @@ -0,0 +1,54 @@ +// 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.Globalization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace RazorWebSite +{ + public class StartupDataAnnotations + { + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + services.AddLocalization(options => options.ResourcesPath = "Resources"); + + services + .AddMvc() + .AddViewLocalization() + .AddDataAnnotationsLocalization((options) => + { + options.DataAnnotationLocalizerProvider = + (modelType, stringLocalizerFactory) => stringLocalizerFactory.Create(typeof(SingleType)); + }); + services.Configure(options => options.CompatibilityVersion = CompatibilityVersion.Latest); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + app.UseRequestLocalization(new RequestLocalizationOptions + { + DefaultRequestCulture = new RequestCulture("en-US", "en-US"), + SupportedCultures = new List + { + new CultureInfo("en-US") + }, + SupportedUICultures = new List + { + new CultureInfo("en-US") + } + }); + app.UseStaticFiles(); + + // Add MVC to the request pipeline + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml b/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml new file mode 100644 index 0000000000..684d00ffa1 --- /dev/null +++ b/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml @@ -0,0 +1,6 @@ +@model RazorWebSite.Models.EnumModel + +
+ @Html.DisplayNameFor(model => model.Id) + @Html.DisplayFor(model => model.Id) +
From 778e137fe986a8520c6968927a5fc1b6d4462e74 Mon Sep 17 00:00:00 2001 From: "Nate McMaster (automated)" Date: Fri, 25 May 2018 16:16:30 -0700 Subject: [PATCH 035/316] Update bootstrapper scripts (automated commit) [ci skip] --- run.ps1 | 9 +++++---- run.sh | 19 +++++++++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/run.ps1 b/run.ps1 index 96c6c54c69..3b27382468 100644 --- a/run.ps1 +++ b/run.ps1 @@ -113,9 +113,9 @@ function Get-KoreBuild { try { $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix - if (Get-Command -Name 'Expand-Archive' -ErrorAction Ignore) { + if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { # Use built-in commands where possible as they are cross-plat compatible - Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath + Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath } else { # Fallback to old approach for old installations of PowerShell @@ -179,8 +179,9 @@ if (Test-Path $ConfigFile) { } } catch { - Write-Warning "$ConfigFile could not be read. Its settings will be ignored." - Write-Warning $Error[0] + Write-Host -ForegroundColor Red $Error[0] + Write-Error "$ConfigFile contains invalid JSON." + exit 1 } } diff --git a/run.sh b/run.sh index 4606a42e78..02aac15874 100755 --- a/run.sh +++ b/run.sh @@ -186,7 +186,7 @@ while [[ $# -gt 0 ]]; do --reinstall|-[Rr]einstall) reinstall=true ;; - --ci) + --ci|-[Cc][Ii]) ci=true ;; --verbose|-Verbose) @@ -220,17 +220,28 @@ if [ -f "$config_file" ]; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else - __warn "$config_file is invalid JSON. Its settings will be ignored." + _error "$config_file contains invalid JSON." + exit 1 fi elif __machine_has python ; then if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - __warn "$config_file is invalid JSON. Its settings will be ignored." + _error "$config_file contains invalid JSON." + exit 1 + fi + elif __machine_has python3 ; then + if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then + config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" + config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" + else + _error "$config_file contains invalid JSON." + exit 1 fi else - __warn 'Missing required command: jq or pyton. Could not parse the JSON file. Its settings will be ignored.' + _error 'Missing required command: jq or python. Could not parse the JSON file.' + exit 1 fi [ ! -z "${config_channel:-}" ] && channel="$config_channel" From dacbb41478cb552a675d0144ff4362b83dbcbca9 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 27 May 2018 19:23:26 +0000 Subject: [PATCH 036/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 150 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3e1941a430..a066dbb6ae 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34255 - 2.2.0-preview1-17060 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 + 2.2.0-preview1-34326 + 2.2.0-preview1-17064 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 5.2.4 2.8.0 2.8.0 - 2.2.0-preview1-34255 + 2.2.0-preview1-34326 1.7.0 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-26509-06 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-26526-03 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 2.0.0 2.1.0-rc1 - 2.2.0-preview1-26509-06 - 2.2.0-preview1-34255 - 2.2.0-preview1-34255 + 2.2.0-preview1-26526-03 + 2.2.0-preview1-34326 + 2.2.0-preview1-34326 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26508-04 - 4.6.0-preview1-26508-04 - 4.6.0-preview1-26508-04 + 4.6.0-preview1-26525-01 + 4.6.0-preview1-26525-01 + 4.6.0-preview1-26525-01 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index cf2fff7def..3028b66761 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17060 -commithash:25b4b134d6f8f7b461928f0d495cfc695ccabb5b +version:2.2.0-preview1-17064 +commithash:5380a2461b135b261646f31d1c919ab0a7b577a8 From 8e313192155a48dff5a981450a1364c19e8b0806 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 4 May 2018 09:05:16 -0700 Subject: [PATCH 037/316] Prevent null refs in some simple cases in CachedExpressionCompiler Fixes #6928 --- .../Internal/ExpressionHelper.cs | 5 +- .../Internal/ExpressionMetadataProvider.cs | 12 +- .../ViewFeatures/CachedExpressionCompiler.cs | 176 ++- .../ViewFeatures/MemberExpressionCacheKey.cs | 90 ++ .../MemberExpressionCacheKeyComparer.cs | 53 + .../MemberExpressionCacheKeyComparerTest.cs | 216 ++++ .../Internal/MemberExpressionCacheKeyTest.cs | 111 ++ .../CachedExpressionCompilerTest.cs | 1012 +++++++++++++++++ 8 files changed, 1638 insertions(+), 37 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs index 90df635a9c..4de9146bb2 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs @@ -242,7 +242,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal try { - func = CachedExpressionCompiler.Process(lambda); + func = CachedExpressionCompiler.Process(lambda) ?? lambda.Compile(); } catch (InvalidOperationException ex) { @@ -259,8 +259,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public static bool IsSingleArgumentIndexer(Expression expression) { - var methodExpression = expression as MethodCallExpression; - if (methodExpression == null || methodExpression.Arguments.Count != 1) + if (!(expression is MethodCallExpression methodExpression) || methodExpression.Arguments.Count != 1) { return false; } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs index 56d7774ad7..0f8b11eacb 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs @@ -82,11 +82,17 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal object modelAccessor(object container) { - var compiledExpression = CachedExpressionCompiler.Process(expression); - Debug.Assert(compiledExpression != null); + var model = (TModel)container; + var cachedFunc = CachedExpressionCompiler.Process(expression); + if (cachedFunc != null) + { + return cachedFunc(model); + } + + var func = expression.Compile(); try { - return compiledExpression((TModel)container); + return func(model); } catch (NullReferenceException) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs index f41448d8f3..03c0b31da9 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs @@ -11,12 +11,16 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures { internal static class CachedExpressionCompiler { - // This is the entry point to the cached expression compilation system. The system - // will try to turn the expression into an actual delegate as quickly as possible, - // relying on cache lookups and other techniques to save time if appropriate. - // If the provided expression is particularly obscure and the system doesn't know - // how to handle it, we'll just compile the expression as normal. - public static Func Process( + private static readonly Expression NullExpression = Expression.Constant(value: null); + + /// + /// This is the entry point to the expression compilation system. The system + /// a) Will rewrite the expression to avoid null refs when any part of the expression tree is evaluated to null + /// b) Attempt to cache the result, or an intermediate part of the result. + /// If the provided expression is particularly obscure and the system doesn't know how to handle it, it will + /// return null. + /// + public static Func Process( Expression> expression) { if (expression == null) @@ -29,15 +33,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private static class Compiler { - private static Func _identityFunc; + private static Func _identityFunc; - private static readonly ConcurrentDictionary> _simpleMemberAccessCache = - new ConcurrentDictionary>(); + private static readonly ConcurrentDictionary> _simpleMemberAccessCache = + new ConcurrentDictionary>(); + + private static readonly ConcurrentDictionary> _chainedMemberAccessCache = + new ConcurrentDictionary>(MemberExpressionCacheKeyComparer.Instance); private static readonly ConcurrentDictionary> _constMemberAccessCache = new ConcurrentDictionary>(); - public static Func Compile(Expression> expression) + public static Func Compile(Expression> expression) { Debug.Assert(expression != null); @@ -55,77 +62,120 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures case MemberExpression memberExpression when memberExpression.Expression is ConstantExpression constantExpression: return CompileCapturedConstant(memberExpression, constantExpression); - // model => StaticMember + // model => ModelType.StaticMember case MemberExpression memberExpression when memberExpression.Expression == null: return CompileFromStaticMemberAccess(expression, memberExpression); // model => model.Member case MemberExpression memberExpression when memberExpression.Expression == expression.Parameters[0]: - return CompileFromMemberAccess(expression, memberExpression); + return CompileFromSimpleMemberAccess(expression, memberExpression); + + // model => model.Member1.Member2 + case MemberExpression memberExpression when IsChainedPropertyAccessor(memberExpression): + return CompileForChainedMemberAccess(expression, memberExpression); default: - return CompileSlow(expression); + return null; + } + + bool IsChainedPropertyAccessor(MemberExpression memberExpression) + { + while (memberExpression.Expression != null) + { + if (memberExpression.Expression is MemberExpression leftExpression) + { + memberExpression = leftExpression; + continue; + } + else if (memberExpression.Expression == expression.Parameters[0]) + { + return true; + } + + break; + } + + return false; } } - private static Func CompileFromConstLookup( + private static Func CompileFromConstLookup( ConstantExpression constantExpression) { // model => {const} - var constantValue = (TResult)constantExpression.Value; + var constantValue = constantExpression.Value; return _ => constantValue; } - private static Func CompileFromIdentityFunc( + private static Func CompileFromIdentityFunc( Expression> expression) { // model => model - // Don't need to lock, as all identity funcs are identical. if (_identityFunc == null) { - _identityFunc = expression.Compile(); + var identityFuncCore = expression.Compile(); + _identityFunc = model => identityFuncCore(model); } return _identityFunc; } - private static Func CompileFromMemberAccess( + private static Func CompileFromStaticMemberAccess( Expression> expression, MemberExpression memberExpression) { - // model => model.Member + // model => ModelType.StaticMember if (_simpleMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) { return result; } - result = expression.Compile(); + var func = expression.Compile(); + result = model => func(model); result = _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, result); + return result; } - private static Func CompileFromStaticMemberAccess( + private static Func CompileFromSimpleMemberAccess( Expression> expression, MemberExpression memberExpression) { - // model => model.StaticMember + // Input: () => m.Member + // Output: () => (m == null) ? null : m.Member if (_simpleMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) { return result; } - result = expression.Compile(); - result = _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, result); + result = _simpleMemberAccessCache.GetOrAdd(memberExpression.Member, Rewrite(expression, memberExpression)); return result; } - private static Func CompileCapturedConstant(MemberExpression memberExpression, ConstantExpression constantExpression) + private static Func CompileForChainedMemberAccess( + Expression> expression, + MemberExpression memberExpression) { - // model => {const}.Member (captured local variable) + // Input: () => m.Member1.Member2 + // Output: () => (m == null || m.Member1 == null) ? null : m.Member1.Member2 + var key = new MemberExpressionCacheKey(typeof(TModel), memberExpression); + if (_chainedMemberAccessCache.TryGetValue(key, out var result)) + { + return result; + } + + var cacheableKey = key.MakeCacheable(); + result = _chainedMemberAccessCache.GetOrAdd(cacheableKey, Rewrite(expression, memberExpression)); + return result; + } + + private static Func CompileCapturedConstant(MemberExpression memberExpression, ConstantExpression constantExpression) + { + // model => {const} (captured local variable) if (!_constMemberAccessCache.TryGetValue(memberExpression.Member, out var result)) { - // rewrite as capturedLocal => ((TDeclaringType)capturedLocal).Member + // rewrite as capturedLocal => ((TDeclaringType)capturedLocal) var parameterExpression = Expression.Parameter(typeof(object), "capturedLocal"); var castExpression = Expression.Convert(parameterExpression, memberExpression.Member.DeclaringType); @@ -142,10 +192,74 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures return _ => result(capturedLocal); } - private static Func CompileSlow(Expression> expression) + private static Func Rewrite( + Expression> expression, + MemberExpression memberExpression) { - // fallback compilation system - just compile the expression directly - return expression.Compile(); + Expression combinedNullTest = null; + var currentExpression = memberExpression; + + while (currentExpression != null) + { + AddNullCheck(currentExpression.Expression, ref combinedNullTest); + + if (currentExpression.Expression is MemberExpression leftExpression) + { + currentExpression = leftExpression; + } + else + { + break; + } + } + + var body = expression.Body; + + // Cast the entire expression to object in case Member is a value type. This is required for us to be able to + // express the null conditional statement m == null ? null : (object)m.IntValue + if (body.Type.IsValueType) + { + body = Expression.Convert(body, typeof(object)); + } + + if (combinedNullTest != null) + { + Debug.Assert(combinedNullTest.Type == typeof(bool)); + body = Expression.Condition( + combinedNullTest, + Expression.Constant(value: null, body.Type), + body); + } + + var rewrittenExpression = Expression.Lambda>(body, expression.Parameters); + return rewrittenExpression.Compile(); + } + + private static void AddNullCheck(Expression invokingExpression, ref Expression combinedNullTest) + { + var type = invokingExpression.Type; + var isNullableValueType = type.IsValueType && Nullable.GetUnderlyingType(type) != null; + if (type.IsValueType && !isNullableValueType) + { + // struct.Member where struct is not nullable. Do nothing. + return; + } + + // NullableStruct.Member or Class.Member + // type is Nullable ? (value == null) : object.ReferenceEquals(value, null) + var nullTest = isNullableValueType ? + Expression.Equal(invokingExpression, NullExpression) : + Expression.ReferenceEqual(invokingExpression, NullExpression); + + if (combinedNullTest == null) + { + combinedNullTest = nullTest; + } + else + { + // m == null || m.Member == null + combinedNullTest = Expression.OrElse(nullTest, combinedNullTest); + } } } } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs new file mode 100644 index 0000000000..1cbc80c38a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + internal struct MemberExpressionCacheKey + { + public MemberExpressionCacheKey(Type modelType, MemberExpression memberExpression) + { + ModelType = modelType; + MemberExpression = memberExpression; + Members = null; + } + + public MemberExpressionCacheKey(Type modelType, MemberInfo[] members) + { + ModelType = modelType; + Members = members; + MemberExpression = null; + } + + // We want to avoid caching a MemberExpression since it has references to other instances in the expression tree. + // We instead store it as a series of MemberInfo items that comprise of the MemberExpression going from right-most + // expression to left. + public MemberExpressionCacheKey MakeCacheable() + { + var members = new List(); + foreach (var member in this) + { + members.Add(member); + } + + return new MemberExpressionCacheKey(ModelType, members.ToArray()); + } + + public MemberExpression MemberExpression { get; } + + public Type ModelType { get; } + + public MemberInfo[] Members { get; } + + public Enumerator GetEnumerator() => new Enumerator(ref this); + + public struct Enumerator + { + private readonly MemberInfo[] _members; + private int _index; + private MemberExpression _memberExpression; + + public Enumerator(ref MemberExpressionCacheKey key) + { + Current = null; + _members = key.Members; + _memberExpression = key.MemberExpression; + _index = -1; + } + + public MemberInfo Current { get; private set; } + + public bool MoveNext() + { + if (_members != null) + { + _index++; + if (_index >= _members.Length) + { + return false; + } + + Current = _members[_index]; + return true; + } + + if (_memberExpression == null) + { + return false; + } + + Current = _memberExpression.Member; + _memberExpression = _memberExpression.Expression as MemberExpression; + return true; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs new file mode 100644 index 0000000000..5911611c89 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + internal class MemberExpressionCacheKeyComparer : IEqualityComparer + { + public static readonly MemberExpressionCacheKeyComparer Instance = new MemberExpressionCacheKeyComparer(); + + public bool Equals(MemberExpressionCacheKey x, MemberExpressionCacheKey y) + { + if (x.ModelType != y.ModelType) + { + return false; + } + + var xEnumerator = x.GetEnumerator(); + var yEnumerator = y.GetEnumerator(); + + while (xEnumerator.MoveNext()) + { + if (!yEnumerator.MoveNext()) + { + return false; + } + + // Current is a MemberInfo instance which has a good comparer. + if (xEnumerator.Current != yEnumerator.Current) + { + return false; + } + } + + return !yEnumerator.MoveNext(); + } + + public int GetHashCode(MemberExpressionCacheKey obj) + { + var hashCodeCombiner = new HashCodeCombiner(); + hashCodeCombiner.Add(obj.ModelType); + + foreach (var member in obj) + { + hashCodeCombiner.Add(member); + } + + return hashCodeCombiner.CombinedHash; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs new file mode 100644 index 0000000000..164b899151 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs @@ -0,0 +1,216 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + public class MemberExpressionCacheKeyComparerTest + { + private readonly MemberExpressionCacheKeyComparer Comparer = MemberExpressionCacheKeyComparer.Instance; + + [Fact] + public void Equals_ReturnsTrue_ForTheSameExpression() + { + // Arrange + var key = GetKey(m => m.Value); + + // Act & Assert + VerifyEquals(key, key); + } + + [Fact] + public void Equals_ReturnsTrue_ForDifferentInstances_OfSameExpression() + { + // Arrange + var key1 = GetKey(m => m.Value); + var key2 = GetKey(m => m.Value); + + // Act & Assert + VerifyEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsTrue_ForChainedMemberAccessExpressionsWithReferenceTypes() + { + // Arrange + var key1 = GetKey(m => m.TestModel2.Name); + var key2 = GetKey(m => m.TestModel2.Name); + + // Act & Assert + VerifyEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsTrue_ForChainedMemberAccessExpressionsWithNullableValueTypes() + { + // Arrange + var key1 = GetKey(m => m.NullableDateTime.Value.TimeOfDay); + var key2 = GetKey(m => m.NullableDateTime.Value.TimeOfDay); + + // Act & Assert + VerifyEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsTrue_ForChainedMemberAccessExpressionsWithValueTypes() + { + // Arrange + var key1 = GetKey(m => m.DateTime.Year); + var key2 = GetKey(m => m.DateTime.Year); + + // Act & Assert + VerifyEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_ForDifferentExpression() + { + // Arrange + var key1 = GetKey(m => m.Value); + var key2 = GetKey(m => m.TestModel2.Name); + + // Act & Assert + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_ForChainedExpressions() + { + // Arrange + var key1 = GetKey(m => m.TestModel2.Id); + var key2 = GetKey(m => m.TestModel2.Name); + + // Act & Assert + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_ForChainedExpressions_WithValueTypes() + { + // Arrange + var key1 = GetKey(m => m.DateTime.Ticks); + var key2 = GetKey(m => m.DateTime.Year); + + // Act & Assert + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_ForChainedExpressions_DifferingByNullable() + { + // Arrange + var key1 = GetKey(m => m.DateTime.Ticks); + var key2 = GetKey(m => m.NullableDateTime.Value.Ticks); + + // Act & Assert + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_WhenOneExpressionIsSubsetOfOther() + { + // Arrange + var key1 = GetKey(m => m.TestModel2); + var key2 = GetKey(m => m.TestModel2.Name); + + // Act & Assert + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_WhenMemberIsAccessedThroughNullableProperty() + { + // Arrange + var key1 = GetKey(m => m.NullableDateTime.Value.Year); + var key2 = GetKey(m => m.DateTime.Year); + + // Act + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_WhenMemberIsAccessedThroughDifferentModels() + { + // Arrange + var key1 = GetKey(m => m.Id); + var key2 = GetKey(m => m.TestModel2.Id); + + // Act + VerifyNotEquals(key1, key2); + } + + [Fact] + public void Equals_ReturnsFalse_WhenMemberIsAccessedThroughConstantExpression() + { + // Arrange + var testModel = new TestModel2 { Id = 1 }; + var key1 = GetKey(m => testModel.Id); + var key2 = GetKey(m => m.Id); + + // Act + VerifyNotEquals(key1, key2); + } + + private void VerifyEquals(MemberExpressionCacheKey key1, MemberExpressionCacheKey key2) + { + Assert.Equal(key1, key2, Comparer); + + var hashCode1 = Comparer.GetHashCode(key1); + var hashCode2 = Comparer.GetHashCode(key2); + Assert.Equal(hashCode1, hashCode2); + + var cachedKey1 = key1.MakeCacheable(); + + Assert.Equal(key1, cachedKey1, Comparer); + Assert.Equal(cachedKey1, key1, Comparer); + + var cachedKeyHashCode1 = Comparer.GetHashCode(cachedKey1); + Assert.Equal(hashCode1, cachedKeyHashCode1); + } + + private void VerifyNotEquals(MemberExpressionCacheKey key1, MemberExpressionCacheKey key2) + { + var hashCode1 = Comparer.GetHashCode(key1); + var hashCode2 = Comparer.GetHashCode(key2); + + Assert.NotEqual(hashCode1, hashCode2); + Assert.NotEqual(key1, key2, Comparer); + + var cachedKey1 = key1.MakeCacheable(); + Assert.NotEqual(key2, cachedKey1, Comparer); + + var cachedKeyHashCode1 = Comparer.GetHashCode(cachedKey1); + Assert.NotEqual(cachedKeyHashCode1, hashCode2); + } + + private static MemberExpressionCacheKey GetKey(Expression> expresssion) + => GetKey(expresssion); + + private static MemberExpressionCacheKey GetKey(Expression> expresssion) + { + var memberExpression = Assert.IsAssignableFrom(expresssion.Body); + return new MemberExpressionCacheKey(typeof(TModel), memberExpression); + } + + public class TestModel + { + public string Value { get; set; } + + public TestModel2 TestModel2 { get; set; } + + public DateTime DateTime { get; set; } + + public DateTime? NullableDateTime { get; set; } + } + + public class TestModel2 + { + public string Name { get; set; } + + public int Id { get; set; } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs new file mode 100644 index 0000000000..5de4c308c3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs @@ -0,0 +1,111 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + public class MemberExpressionCacheKeyTest + { + [Fact] + public void GetEnumerator_ReturnsMembers() + { + // Arrange + var expected = new[] + { + typeof(TestModel3).GetProperty(nameof(TestModel3.Value)), + typeof(TestModel2).GetProperty(nameof(TestModel2.TestModel3)), + typeof(TestModel).GetProperty(nameof(TestModel.TestModel2)), + }; + + var key = GetKey(m => m.TestModel2.TestModel3.Value); + + // Act + var actual = GetMembers(key); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void GetEnumerator_WithNullableType_ReturnsMembers() + { + // Arrange + var expected = new[] + { + typeof(DateTime).GetProperty(nameof(DateTime.Ticks)), + typeof(DateTime?).GetProperty(nameof(Nullable.Value)), + typeof(TestModel).GetProperty(nameof(TestModel.NullableDateTime)), + }; + + var key = GetKey(m => m.NullableDateTime.Value.Ticks); + + // Act + var actual = GetMembers(key); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void GetEnumerator_WithValueType_ReturnsMembers() + { + // Arrange + var expected = new[] + { + typeof(DateTime).GetProperty(nameof(DateTime.Ticks)), + typeof(TestModel).GetProperty(nameof(TestModel.DateTime)), + }; + + var key = GetKey(m => m.DateTime.Ticks); + + // Act + var actual = GetMembers(key); + + // Assert + Assert.Equal(expected, actual); + } + + private static MemberExpressionCacheKey GetKey(Expression> expresssion) + { + var memberExpression = Assert.IsAssignableFrom(expresssion.Body); + return new MemberExpressionCacheKey(typeof(TestModel), memberExpression); + } + + private static IList GetMembers(MemberExpressionCacheKey key) + { + var members = new List(); + foreach (var member in key) + { + members.Add(member); + } + + return members; + } + + public class TestModel + { + public TestModel2 TestModel2 { get; set; } + + public DateTime DateTime { get; set; } + + public DateTime? NullableDateTime { get; set; } + } + + public class TestModel2 + { + public string Name { get; set; } + + public TestModel3 TestModel3 { get; set; } + } + + public class TestModel3 + { + public string Value { get; set; } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs new file mode 100644 index 0000000000..a798d5594e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs @@ -0,0 +1,1012 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq.Expressions; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + public class CachedExpressionCompilerTest + { + [Fact] + public void Process_IdentityExpression() + { + // Arrange + var model = new TestModel(); + var expression = GetTestModelExpression(m => m); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Same(model, result); + } + + [Fact] + public void Process_CachesIdentityExpression() + { + // Arrange + var expression1 = GetTestModelExpression(m => m); + var expression2 = GetTestModelExpression(m => m); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_ConstLookup() + { + // Arrange + var model = new TestModel(); + var differentModel = new DifferentModel(); + var expression = GetTestModelExpression(m => differentModel); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Same(differentModel, result); + } + + [Fact] + public void Process_ConstLookup_ReturningNull() + { + // Arrange + var model = new TestModel(); + var expression = GetTestModelExpression(m => (DifferentModel)null); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ConstLookup_WithNullModel() + { + // Arrange + var differentModel = new DifferentModel(); + var expression = GetTestModelExpression(m => differentModel); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(null); + Assert.Same(differentModel, result); + } + + [Fact] + public void Process_ConstLookup_UsingCachedValue() + { + // Arrange + var model = new TestModel(); + var differentModel = new DifferentModel(); + var expression1 = GetTestModelExpression(m => differentModel); + var expression2 = GetTestModelExpression(m => differentModel); + + // Act - 1 + var func1 = CachedExpressionCompiler.Process(expression1); + + // Assert - 1 + var result1 = func1(null); + Assert.Same(differentModel, result1); + + // Act - 2 + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert - 2 + var result2 = func1(null); + Assert.Same(differentModel, result2); + } + + [Fact] + public void Process_ConstLookup_WhenCapturedLocalChanges() + { + // Arrange + var model = new TestModel(); + var differentModel = new DifferentModel(); + var expression = GetTestModelExpression(m => differentModel); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert - 1 + var result1 = func(null); + Assert.Same(differentModel, result1); + + // Act - 2 + differentModel = new DifferentModel(); + + // Assert - 2 + var result2 = func(null); + Assert.NotSame(differentModel, result1); + Assert.Same(differentModel, result2); + } + + [Fact] + public void Process_ConstLookup_WithPrimitiveConstant() + { + // Arrange + var model = new TestModel(); + var expression = GetTestModelExpression(m => 10); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(10, result); + } + + [Fact] + public void Process_StaticFieldAccess() + { + // Arrange + var model = new TestModel(); + var expression = GetTestModelExpression(m => TestModel.StaticField); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal("StaticValue", result); + } + + [Fact] + public void Process_CachesStaticFieldAccess() + { + // Arrange + var expression1 = GetTestModelExpression(m => TestModel.StaticField); + var expression2 = GetTestModelExpression(m => TestModel.StaticField); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_StaticPropertyAccess() + { + // Arrange + var expected = "TestValue"; + TestModel.StaticProperty = expected; + var model = new TestModel(); + var expression = GetTestModelExpression(m => TestModel.StaticProperty); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_CachesStaticPropertyAccess() + { + // Arrange + var expression1 = GetTestModelExpression(m => TestModel.StaticProperty); + var expression2 = GetTestModelExpression(m => TestModel.StaticProperty); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_StaticPropertyAccess_WithNullModel() + { + // Arrange + var expected = "TestValue"; + TestModel.StaticProperty = expected; + var expression = GetTestModelExpression(m => TestModel.StaticProperty); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(null); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ConstFieldLookup() + { + // Arrange + var model = new TestModel(); + var expression = GetTestModelExpression(m => DifferentModel.Constant); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(10, result); + } + + [Fact] + public void Process_ConstFieldLookup_WthNullModel() + { + // Arrange + var expression = GetTestModelExpression(m => DifferentModel.Constant); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(null); + Assert.Equal(10, result); + } + + [Fact] + public void Process_SimpleMemberAccess() + { + // Arrange + var model = new TestModel { Name = "Test" }; + var expression = GetTestModelExpression(m => m.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal("Test", result); + } + + [Fact] + public void Process_CachesSimpleMemberAccess() + { + // Arrange + var expression1 = GetTestModelExpression(m => m.Name); + var expression2 = GetTestModelExpression(m => m.Name); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_SimpleMemberAccess_ToPrimitive() + { + // Arrange + var model = new TestModel { Age = 12 }; + var expression = GetTestModelExpression(m => m.Age); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(12, result); + } + + [Fact] + public void Process_SimpleMemberAccess_WithNullModel() + { + // Arrange + var model = (TestModel)null; + var expression = GetTestModelExpression(m => m.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_SimpleMemberAccess_ToPrimitive_WithNullModel() + { + // Arrange + var model = (TestModel)null; + var expression = GetTestModelExpression(m => m.Age); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer() + { + // Arrange + var model = new BadEqualityModel { Id = 7 }; + var expression = GetExpression(m => m.Id); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(7, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithNullModel() + { + // Arrange + var model = (BadEqualityModel)null; + var expression = GetExpression(m => m.Id); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnValueTypeWithBadEqualityComparer() + { + // Arrange + var model = new BadEqualityValueTypeModel { Id = 7 }; + var expression = GetExpression(m => m.Id); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(7, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnTypeWithBadEqualityComparer_WithDefaultValue() + { + // Arrange + var model = (BadEqualityValueTypeModel)default; + var expression = GetExpression(m => m.Id); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(model.Id, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnValueType() + { + // Arrange + var model = new DateTime(2000, 1, 1); + var expression = GetExpression(m => m.Year); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(2000, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnValueType_WithDefaultValue() + { + // Arrange + var model = default(DateTime); + var expression = GetExpression(m => m.Year); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(1, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnNullableValueType() + { + // Arrange + var model = new DateTime(2000, 1, 1); + var nullableModel = (DateTime?)model; + var expression = GetExpression(m => m.Value); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(nullableModel); + Assert.Equal(model, result); + } + + [Fact] + public void Process_SimpleMemberAccess_OnNullableValueType_WithNullValue() + { + // Arrange + var nullableModel = (DateTime?)null; + var expression = GetExpression(m => m.Value); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(nullableModel); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_ToValueType() + { + // Arrange + var dateTime = new DateTime(2000, 1, 1); + var model = new TestModel { Date = dateTime }; + var expression = GetTestModelExpression(m => m.Date.Year); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(2000, result); + } + + [Fact] + public void Process_ChainedMemberAccess_ToValueType_WithNullModel() + { + // Arrange + var model = (TestModel)null; + var expression = GetTestModelExpression(m => m.Date.Year); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_ToReferenceType() + { + // Arrange + var expected = "Test1"; + var model = new TestModel { DifferentModel = new DifferentModel { Name = expected } }; + var expression = GetTestModelExpression(m => m.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_CachesChainedMemberAccess() + { + // Arrange + var expression1 = GetTestModelExpression(m => m.DifferentModel.Name); + var expression2 = GetTestModelExpression(m => m.DifferentModel.Name); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_CachesChainedMemberAccess_ToValueType() + { + // Arrange + var expression1 = GetTestModelExpression(m => m.Date.Year); + var expression2 = GetTestModelExpression(m => m.Date.Year); + + // Act + var func1 = CachedExpressionCompiler.Process(expression1); + var func2 = CachedExpressionCompiler.Process(expression2); + + // Assert + Assert.NotNull(func1); + Assert.Same(func1, func2); + } + + [Fact] + public void Process_ChainedMemberAccess_LongChain_WithReferenceType() + { + // Arrange + var expected = "TestVal"; + var model = new Chain0Model + { + Chain1 = new Chain1Model + { + ValueTypeModel = new ValueType1 + { + TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } } + } + } + }; + + var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ChainedMemberAccess_LongChain_WithNullIntermediary() + { + // Arrange + var model = new Chain0Model + { + Chain1 = new Chain1Model + { + ValueTypeModel = new ValueType1 { TestModel = null }, + } + }; + + var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_LongChain_WithNullValueTypeAccessor() + { + // Arrange + // Chain2 is a value type + var model = new Chain0Model + { + Chain1 = null + }; + + var expression = GetExpression(m => m.Chain1.ValueTypeModel.TestModel.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_LongChain_WithNullableValueType() + { + // Arrange + var expected = "TestVal"; + var model = new Chain0Model + { + Chain1 = new Chain1Model + { + NullableValueTypeModel = new ValueType1 + { + TestModel = new TestModel { DifferentModel = new DifferentModel { Name = expected } } + } + } + }; + + var expression = GetExpression(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ChainedMemberAccess_LongChain_WithNullValuedNullableValueType() + { + // Arrange + var model = new Chain0Model + { + Chain1 = new Chain1Model + { + NullableValueTypeModel = null + } + }; + + var expression = GetExpression(m => m.Chain1.NullableValueTypeModel.Value.TestModel.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_ToReferenceType_WithNullIntermediary() + { + // Arrange + var model = new TestModel { DifferentModel = null }; + var expression = GetTestModelExpression(m => m.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_ToReferenceType_WithNullModel() + { + // Arrange + var model = (TestModel)null; + var expression = GetTestModelExpression(m => m.DifferentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_OfValueTypes_ReturningReferenceTypeMember() + { + // Arrange + var expected = "TestName"; + var model = new ValueType1 + { + ValueType2 = new ValueType2 { Name = expected }, + }; + var expression = GetExpression(m => m.ValueType2.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ChainedMemberAccess_OfValueTypes_ReturningValueType() + { + // Arrange + var expected = new DateTime(2001, 1, 1); + var model = new ValueType1 + { + ValueType2 = new ValueType2 { Date = expected }, + }; + var expression = GetExpression(m => m.ValueType2.Date); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ChainedMemberAccess_OfValueTypes_IncludingNullableType() + { + // Arrange + var expected = "TestName"; + var model = new ValueType1 + { + NullableValueType2 = new ValueType2 { Name = expected }, + }; + var expression = GetExpression(m => m.NullableValueType2.Value.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Equal(expected, result); + } + + [Fact] + public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable() + { + // Arrange + var model = new ValueType1 { NullableValueType2 = null }; + var expression = GetExpression(m => m.NullableValueType2.Value.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_ChainedMemberAccess_OfValueTypes_WithNullValuedNullable_ReturningValueType() + { + // Arrange + var model = new ValueType1 { NullableValueType2 = null }; + var expression = GetExpression(m => m.NullableValueType2.Value.Date); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Null(result); + } + + [Fact] + public void Process_MemberAccessOnCapturedVariable_ReturnsNull() + { + // Arrange + var differentModel = new DifferentModel { Name = "Test" }; + var expression = GetTestModelExpression(m => differentModel.Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.Null(func); + } + + [Fact] + public void Process_CapturedVariable() + { + // Arrange + var differentModel = new DifferentModel(); + var model = new TestModel(); + var expression = GetTestModelExpression(m => differentModel); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Same(differentModel, result); + } + + [Fact] + public void Process_CapturedVariable_WithNullModel() + { + // Arrange + var differentModel = new DifferentModel(); + var model = (TestModel)null; + var expression = GetTestModelExpression(m => differentModel); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.NotNull(func); + var result = func(model); + Assert.Same(differentModel, result); + } + + + [Fact] + public void Process_MemberAccess_OnCapturedVariable_ReturnsNull() + { + // Arrange + var differentModel = "Hello world"; + var expression = GetTestModelExpression(m => differentModel.Length); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.Null(func); + } + + [Fact] + public void Process_ComplexChainedMemberAccess_ReturnsNull() + { + // Arrange + var expected = "SomeName"; + var model = new TestModel { DifferentModels = new[] { new DifferentModel { Name = expected } } }; + var expression = GetTestModelExpression(m => m.DifferentModels[0].Name); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.Null(func); + } + + [Fact] + public void Process_ArrayMemberAccess_ReturnsNull() + { + // Arrange + var expression = GetTestModelExpression(m => m.Sizes[1]); + + // Act + var func = CachedExpressionCompiler.Process(expression); + + // Assert + Assert.Null(func); + } + + private static Expression> GetExpression(Expression> expression) + => expression; + + private static Expression> GetTestModelExpression(Expression> expression) + => GetExpression(expression); + + public class TestModel + { + public static readonly string StaticField = "StaticValue"; + + public static string StaticProperty { get; set; } + + public int Age { get; set; } + + public string Name { get; set; } + + public DateTime Date { get; set; } + + public DifferentModel DifferentModel { get; set; } + + public int[] Sizes { get; set; } + + public DifferentModel[] DifferentModels { get; set; } + } + + public class DifferentModel + { + public const int Constant = 10; + + public string Name { get; set; } + } + + public class Chain0Model + { + public Chain1Model Chain1 { get; set; } + } + + public class Chain1Model + { + public ValueType1 ValueTypeModel { get; set; } + + public ValueType1? NullableValueTypeModel { get; set; } + } + + public struct ValueType1 + { + public TestModel TestModel { get; set; } + + public ValueType2 ValueType2 { get; set; } + + public ValueType2? NullableValueType2 { get; set; } + } + + public struct ValueType2 + { + public string Name { get; set; } + + public DateTime Date { get; set; } + } + + public class BadEqualityModel + { + public int Id { get; set; } + + public override bool Equals(object obj) + { + return this == obj; + } + + public static bool operator ==(BadEqualityModel a, object b) + { + if (a is null || b is null) + { + throw new TimeZoneNotFoundException(); + } + + return true; + } + + public static bool operator !=(BadEqualityModel a, object b) + { + return !(a == b); + } + + public override int GetHashCode() => 0; + } + + public struct BadEqualityValueTypeModel + { + public int Id { get; set; } + + public override bool Equals(object obj) + { + return this == obj; + } + + public static bool operator ==(BadEqualityValueTypeModel a, object b) + { + if (b is null) + { + throw new TimeZoneNotFoundException(); + } + + return true; + } + + public static bool operator !=(BadEqualityValueTypeModel a, object b) + { + return !(a == b); + } + + public override int GetHashCode() => 0; + } + } +} From 632425d0e632c3aa03525aea0c03c5832bbc459f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 29 May 2018 11:20:35 -0700 Subject: [PATCH 038/316] Unskip skipped tests --- .../FileResultTest.cs | 20 +++++++------------ .../ControllerActionInvokerCacheTest.cs | 6 ++---- .../Internal/PageRouteModelFactoryTest.cs | 9 +++------ 3 files changed, 12 insertions(+), 23 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs index da6808d055..8e467419b2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs @@ -30,8 +30,7 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal("text/plain", result.ContentType.ToString()); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task ContentDispositionHeader_IsEncodedCorrectly() { // See comment in FileResult.cs detailing how the FileDownloadName should be encoded. @@ -55,8 +54,7 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(@"attachment; filename=""some\\file""; filename*=UTF-8''some%5Cfile", httpContext.Response.Headers["Content-Disposition"]); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task ContentDispositionHeader_IsEncodedCorrectly_ForUnicodeCharacters() { // Arrange @@ -78,8 +76,7 @@ namespace Microsoft.AspNetCore.Mvc httpContext.Response.Headers["Content-Disposition"]); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task ExecuteResultAsync_DoesNotSetContentDisposition_IfNotSpecified() { // Arrange @@ -104,8 +101,7 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(Stream.Null, httpContext.Response.Body); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task ExecuteResultAsync_SetsContentDisposition_IfSpecified() { // Arrange @@ -126,8 +122,7 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal("attachment; filename=filename.ext; filename*=UTF-8''filename.ext", httpContext.Response.Headers["Content-Disposition"]); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task ExecuteResultAsync_ThrowsException_IfCannotResolveLoggerFactory() { // Arrange @@ -234,8 +229,7 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(expectedOutput, actual); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public async Task SetsAcceptRangeHeader() { // Arrange @@ -538,4 +532,4 @@ namespace Microsoft.AspNetCore.Mvc } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs index 9243142b58..247206469f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs @@ -21,8 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class ControllerActionInvokerCacheTest { - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public void GetControllerActionMethodExecutor_CachesFilters() { // Arrange @@ -43,8 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(cacheEntry1.filters, cacheEntry2.filters); } - [ConditionalFact] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Fact] public void GetControllerActionMethodExecutor_CachesEntry() { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs index 64e9156fa5..7269743cc8 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs @@ -165,8 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }); } - [ConditionalTheory] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Theory] [InlineData("/Areas/About.cshtml")] [InlineData("/Areas/MyArea/Index.cshtml")] public void TryParseAreaPath_ReturnsFalse_IfPathDoesNotConform(string path) @@ -182,8 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.False(success); } - [ConditionalTheory] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Theory] [InlineData("/Areas/MyArea/Views/About.cshtml")] [InlineData("/Areas/MyArea/SubDir/Pages/Index.cshtml")] [InlineData("/Areas/MyArea/NotPages/SubDir/About.cshtml")] @@ -200,8 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.False(success); } - [ConditionalTheory] - [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "Fails due to dotnet/standard#567")] + [Theory] [InlineData("/Areas/MyArea/Pages/Index.cshtml", "MyArea", "/Index")] [InlineData("/Areas/Accounts/Pages/Manage/Edit.cshtml", "Accounts", "/Manage/Edit")] public void TryParseAreaPath_ParsesAreaPath( From f0c552ee49d3348b838961c9947605610f70ec8d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 31 May 2018 13:47:39 -0700 Subject: [PATCH 039/316] Fixup DebuggerDisplay for PageActionDescriptor --- src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs index bf4780d165..eaa15ff9ef 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs @@ -81,6 +81,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages } } - private string DebuggerDisplayString() => $"{{ViewEnginePath = {nameof(ViewEnginePath)}, RelativePath = {nameof(RelativePath)}}}"; + private string DebuggerDisplayString => $"{{ViewEnginePath = {nameof(ViewEnginePath)}, RelativePath = {nameof(RelativePath)}}}"; } } \ No newline at end of file From 839223756b2ad0976b1af64cc3be3f65fc09f2b5 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 3 Jun 2018 19:22:37 +0000 Subject: [PATCH 040/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 154 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 79 insertions(+), 79 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index a066dbb6ae..366a541102 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34326 - 2.2.0-preview1-17064 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 5.2.4 + 2.2.0-preview1-34373 + 2.2.0-preview1-17067 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34326 + 2.2.0-preview1-34373 1.7.0 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-26526-03 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-26531-03 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 2.0.0 - 2.1.0-rc1 - 2.2.0-preview1-26526-03 - 2.2.0-preview1-34326 - 2.2.0-preview1-34326 + 2.1.0 + 2.2.0-preview1-26531-03 + 2.2.0-preview1-34373 + 2.2.0-preview1-34373 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26525-01 - 4.6.0-preview1-26525-01 - 4.6.0-preview1-26525-01 + 4.6.0-preview1-26531-03 + 4.6.0-preview1-26531-03 + 4.6.0-preview1-26531-03 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3028b66761..06ba6285b7 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17064 -commithash:5380a2461b135b261646f31d1c919ab0a7b577a8 +version:2.2.0-preview1-17067 +commithash:2af0e2e3d02329b4f0290061ab9bd8c7ca1aa26f From 592ed3b4f52f87d842b17a4a34a4c10fc6da57c4 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 1 Jun 2018 14:29:34 -0700 Subject: [PATCH 041/316] Create an analyzer to warn users not to decorate filters on page handler methods Fixes #7684 --- ...esShouldNotBeAppliedToPageModelAnalyzer.cs | 166 ++++++++++++++++++ .../CodeAnalysisExtensions.cs | 80 +++++++++ .../DiagnosticDescriptors.cs | 27 +++ .../SymbolNames.cs | 12 +- ...ouldNotBeAppliedToPageModelAnalyzerTest.cs | 120 +++++++++++++ ...owAnonymousIsAppliedToPageHandlerMethod.cs | 18 ++ ...Returned_IfAttributeIsAppliedToBaseType.cs | 17 ++ ...zeAttributeIsAppliedToPageHandlerMethod.cs | 13 ++ ..._IfFiltersAreAppliedToPageHandlerMethod.cs | 13 ++ ...ageHandlerMethodDerivingFromCustomModel.cs | 20 +++ ...dlerMethodForTypeWithPageModelAttribute.cs | 14 ++ ...d_IfRouteAttribute_IsAppliedToPageModel.cs | 12 ++ ...AttributesAreAppliedToPageHandlerMethod.cs | 12 ++ ...nosticsAreReturned_ForControllerActions.cs | 13 ++ ...icsAreReturned_ForControllerBaseActions.cs | 13 ++ ...rned_ForNonHandlerMethodsWithAttributes.cs | 20 +++ ..._ForPageHandlersWithNonFilterAttributes.cs | 13 ++ ...ed_IfAllowAnonymousIsAppliedToPageModel.cs | 13 ++ ...fAuthorizeAttributeIsAppliedToPageModel.cs | 13 ++ ...Returned_IfFiltersAreAppliedToPageModel.cs | 13 ++ 20 files changed, 621 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs new file mode 100644 index 0000000000..4dc7b6c85d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs @@ -0,0 +1,166 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class AttributesShouldNotBeAppliedToPageModelAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods, + DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods, + DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(compilationStartAnalysisContext => + { + var typeCache = new TypeCache(compilationStartAnalysisContext.Compilation); + if (typeCache.PageModelAttribute == null || typeCache.PageModelAttribute.TypeKind == TypeKind.Error) + { + // No-op if we can't find types we care about. + return; + } + + InitializeWorker(compilationStartAnalysisContext, typeCache); + }); + } + + private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, TypeCache typeCache) + { + compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext => + { + var method = (IMethodSymbol)symbolAnalysisContext.Symbol; + + var declaringType = method.ContainingType; + if (!IsPageModel(declaringType, typeCache.PageModelAttribute) || !IsPageHandlerMethod(method)) + { + return; + } + + ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.IFilterMetadata); + ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.AuthorizeAttribute); + ReportFilterDiagnostic(ref symbolAnalysisContext, method, typeCache.AllowAnonymousAttribute); + + ReportRouteDiagnostic(ref symbolAnalysisContext, method, typeCache.IRouteTemplateProvider); + }, SymbolKind.Method); + + compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext => + { + var type = (INamedTypeSymbol)symbolAnalysisContext.Symbol; + if (!IsPageModel(type, typeCache.PageModelAttribute)) + { + return; + } + + ReportRouteDiagnosticOnModel(ref symbolAnalysisContext, type, typeCache.IRouteTemplateProvider); + }, SymbolKind.NamedType); + } + + private bool IsPageHandlerMethod(IMethodSymbol method) + { + return method.MethodKind == MethodKind.Ordinary && + !method.IsStatic && + !method.IsGenericMethod && + method.DeclaredAccessibility == Accessibility.Public; + } + + private static bool IsPageModel(INamedTypeSymbol type, INamedTypeSymbol pageAttributeModel) + { + return type.TypeKind == TypeKind.Class && + !type.IsStatic && + type.HasAttribute(pageAttributeModel, inherit: true); + } + + private static void ReportRouteDiagnosticOnModel(ref SymbolAnalysisContext symbolAnalysisContext, INamedTypeSymbol typeSymbol, INamedTypeSymbol routeAttribute) + { + var attribute = GetAttribute(typeSymbol, routeAttribute); + if (attribute != null) + { + var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); + + symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels, + location, + attribute.AttributeClass.Name)); + } + } + + private static void ReportRouteDiagnostic(ref SymbolAnalysisContext symbolAnalysisContext, IMethodSymbol method, INamedTypeSymbol routeAttribute) + { + var attribute = GetAttribute(method, routeAttribute); + if (attribute != null) + { + var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); + + symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods, + location, + attribute.AttributeClass.Name)); + } + } + + private static void ReportFilterDiagnostic(ref SymbolAnalysisContext symbolAnalysisContext, IMethodSymbol method, INamedTypeSymbol filterAttribute) + { + var attribute = GetAttribute(method, filterAttribute); + if (attribute != null) + { + var location = GetAttributeLocation(ref symbolAnalysisContext, attribute); + + symbolAnalysisContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods, + location, + attribute.AttributeClass.Name)); + } + } + + private static AttributeData GetAttribute(ISymbol symbol, INamedTypeSymbol attributeType) + { + foreach (var attribute in symbol.GetAttributes()) + { + if (attributeType.IsAssignableFrom(attribute.AttributeClass)) + { + return attribute; + } + } + + return null; + } + + private static Location GetAttributeLocation(ref SymbolAnalysisContext symbolAnalysisContext, AttributeData attribute) + { + var syntax = attribute.ApplicationSyntaxReference.GetSyntax(symbolAnalysisContext.CancellationToken); + return syntax?.GetLocation() ?? Location.None; + } + + private class TypeCache + { + public TypeCache(Compilation compilation) + { + PageModelAttribute = compilation.GetTypeByMetadataName(SymbolNames.PageModelAttributeType); + IFilterMetadata = compilation.GetTypeByMetadataName(SymbolNames.IFilterMetadataType); + AuthorizeAttribute = compilation.GetTypeByMetadataName(SymbolNames.AuthorizeAttribute); + AllowAnonymousAttribute = compilation.GetTypeByMetadataName(SymbolNames.AllowAnonymousAttribute); + IRouteTemplateProvider = compilation.GetTypeByMetadataName(SymbolNames.IRouteTemplateProvider); + } + + public INamedTypeSymbol PageModelAttribute { get; } + + public INamedTypeSymbol IFilterMetadata { get; } + + public INamedTypeSymbol AuthorizeAttribute { get; } + + public INamedTypeSymbol AllowAnonymousAttribute { get; } + + public INamedTypeSymbol IRouteTemplateProvider { get; } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs new file mode 100644 index 0000000000..c473cc091d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -0,0 +1,80 @@ +// 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.Diagnostics; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal static class CodeAnalysisExtensions + { + public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) + { + foreach (var type in typeSymbol.GetTypeHierarchy()) + { + if (type.HasAttribute(attribute)) + { + return true; + } + } + + return false; + } + + public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) + { + Debug.Assert(symbol != null); + Debug.Assert(attribute != null); + + foreach (var declaredAttribute in symbol.GetAttributes()) + { + if (declaredAttribute.AttributeClass == attribute) + { + return true; + } + } + + return false; + } + + public static bool IsAssignableFrom(this ITypeSymbol source, INamedTypeSymbol target) + { + Debug.Assert(source != null); + Debug.Assert(target != null); + + if (source.TypeKind == TypeKind.Interface) + { + foreach (var @interface in target.AllInterfaces) + { + if (source == @interface) + { + return true; + } + } + + return false; + } + + foreach (var type in target.GetTypeHierarchy()) + { + if (source == type) + { + return true; + } + } + + return false; + } + + private static IEnumerable GetTypeHierarchy(this ITypeSymbol typeSymbol) + { + while (typeSymbol != null) + { + yield return typeSymbol; + + typeSymbol = typeSymbol.BaseType; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs index 89465662b4..b2684297bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs @@ -15,5 +15,32 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods = + new DiagnosticDescriptor( + "MVC1001", + "Filters cannot be applied to page handler methods.", + "'{0}' cannot be applied to Razor Page handler methods. It may be applied either to the Razor Page model or applied globally.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods = + new DiagnosticDescriptor( + "MVC1002", + "Route attributes cannot be applied to page handler methods.", + "'{0}' cannot be applied to Razor Page handler methods. Routes for Razor Pages must be declared using the @page directive or using conventions.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1003_RouteAttributesShouldNotBeAppliedToPageModels = + new DiagnosticDescriptor( + "MVC1003", + "Route attributes cannot be applied to page models.", + "'{0}' cannot be applied to a Razor Page model. Routes for Razor Pages must be declared using the @page directive or using conventions.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index 9c49dd24e6..9e9065df04 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -5,12 +5,22 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { internal static class SymbolNames { - public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; + public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; + + public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; + + public const string IFilterMetadataType = "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata"; public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; + public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; + + public const string PageModelAttributeType = "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageModelAttribute"; + public const string PartialMethod = "Partial"; public const string RenderPartialMethod = "RenderPartial"; + + public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs new file mode 100644 index 0000000000..1bb389e166 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs @@ -0,0 +1,120 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class AttributesShouldNotBeAppliedToPageModelAnalyzerTest + { + private MvcDiagnosticAnalyzerRunner Executor { get; } = new MvcDiagnosticAnalyzerRunner(new AttributesShouldNotBeAppliedToPageModelAnalyzer()); + + [Fact] + public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() + { + // Act + var result = await Executor.GetDiagnosticsAsync(source: string.Empty); + + // Assert + Assert.Empty(result); + } + + [Fact] + public Task NoDiagnosticsAreReturned_ForControllerBaseActions() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForControllerActions() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes() + => VerifyNoDiagnosticsAreReturned(); + + [Fact] + public Task DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod() + => VerifyDefault(DiagnosticDescriptors.MVC1002_RouteAttributesShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod() + => VerifyDefault(DiagnosticDescriptors.MVC1001_FiltersShouldNotBeAppliedToPageHandlerMethods); + + [Fact] + public Task DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel() + => VerifyDefault(DiagnosticDescriptors.MVC1003_RouteAttributesShouldNotBeAppliedToPageModels); + + private async Task VerifyNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") + { + // Arrange + var source = MvcTestSource.Read(GetType().Name, testMethod); + + // Act + var result = await Executor.GetDiagnosticsAsync(source.Source); + + // Assert + Assert.Empty(result); + } + + private async Task VerifyDefault(DiagnosticDescriptor descriptor, [CallerMemberName] string testMethod = "") + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Executor.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Collection( + result, + diagnostic => + { + + Assert.Equal(descriptor.Id, diagnostic.Id); + Assert.Same(descriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs new file mode 100644 index 0000000000..45ef30ce92 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod : PageModel + { + [/*MM*/AllowAnonymous] + public void OnGet() + { + + } + + public void OnPost() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs new file mode 100644 index 0000000000..9ca5e1e251 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [PageModel] + public abstract class DiagnosticsAreReturned_IfAttributeIsAppliedToBaseTypeBase + { + [/*MM*/Authorize] + public void OnGet() { } + } + + public class DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType : DiagnosticsAreReturned_IfAttributeIsAppliedToBaseTypeBase + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs new file mode 100644 index 0000000000..7bbfe32c07 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod : PageModel + { + [/*MM*/Authorize] + public void OnPost() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs new file mode 100644 index 0000000000..d91567c8cc --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod : PageModel + { + [/*MM*/ServiceFilter(typeof(object))] + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs new file mode 100644 index 0000000000..946d5e42be --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs @@ -0,0 +1,20 @@ +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [PageModel] + public abstract class CustomPageModel + { + + } + + public class DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel : CustomPageModel + { + [/*MM*/ServiceFilter(typeof(object))] + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs new file mode 100644 index 0000000000..1a07dfe84a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs @@ -0,0 +1,14 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [PageModel] + public class DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute + { + [/*MM*/ServiceFilter(typeof(object))] + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs new file mode 100644 index 0000000000..3d865c3269 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [/*MM*/Route("/mypage")] + public class DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs new file mode 100644 index 0000000000..889272bc2c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs @@ -0,0 +1,12 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod : PageModel + { + [/*MM*/HttpHead] + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs new file mode 100644 index 0000000000..c187cc2ff3 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class NoDiagnosticsAreReturned_ForControllerActions : Controller + { + [Authorize] + public IActionResult AuthorizeAttribute() => null; + + [ServiceFilter(typeof(object))] + public IActionResult ServiceFilter() => null; + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs new file mode 100644 index 0000000000..ce077e6b9f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class NoDiagnosticsAreReturned_ForControllerBaseActions : ControllerBase + { + [Authorize] + public IActionResult AuthorizeAttribute() => null; + + [ServiceFilter(typeof(object))] + public IActionResult ServiceFilter() => null; + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs new file mode 100644 index 0000000000..b3aa481c2d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes : PageModel + { + [Authorize] + private void OnGetPrivate() { } + + [TypeFilter(typeof(object))] + internal IActionResult OnPost() => null; + + [AllowAnonymous] + public void OnGet() { } + + [ServiceFilter(typeof(object))] + public static void OnPostStatic() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs new file mode 100644 index 0000000000..3e593db2d4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs @@ -0,0 +1,13 @@ +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + public class NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes : PageModel + { + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs new file mode 100644 index 0000000000..c298c43ff2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [AllowAnonymous] + public class NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs new file mode 100644 index 0000000000..47c778eb9e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [Authorize] + public class NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel : PageModel + { + public void OnGet() + { + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs new file mode 100644 index 0000000000..5d0198174c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +{ + [ServiceFilter(typeof(object))] + public class NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel : PageModel + { + public void OnGet() + { + } + } +} From 672c794335266cdc40efc0382bc3e2ff096e625b Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Tue, 5 Jun 2018 22:33:57 -0700 Subject: [PATCH 042/316] Add certificate names for code signing --- Directory.Build.props | 2 ++ korebuild-lock.txt | 4 ++-- .../Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj | 4 ++++ .../Microsoft.AspNetCore.Mvc.Analyzers.csproj | 4 ++++ .../Microsoft.AspNetCore.Mvc.Razor.csproj | 4 ++++ 5 files changed, 16 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index fad2bb0610..d37be59e20 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,6 +14,8 @@ $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true + Microsoft + MicrosoftNuGet true true diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 06ba6285b7..b679b80427 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17067 -commithash:2af0e2e3d02329b4f0290061ab9bd8c7ca1aa26f +version:2.2.0-preview1-17075 +commithash:d9f07c7f313a0af1d49f003f5424b4dbbdd3e09f diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj index 62ac6da6a1..a124203417 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj @@ -18,6 +18,10 @@ + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj index 310c88685a..bfaa4f6135 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj @@ -14,6 +14,10 @@ + + + + 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 74e943a8d9..f08f8465c9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj @@ -25,6 +25,10 @@ + + + + From 379345b530697ef11d86cc778c28e660696d8b77 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 5 Jun 2018 16:48:31 -0700 Subject: [PATCH 043/316] Add vsts build definition --- .vsts-pipelines/builds/ci.yml | 46 +++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 .vsts-pipelines/builds/ci.yml diff --git a/.vsts-pipelines/builds/ci.yml b/.vsts-pipelines/builds/ci.yml new file mode 100644 index 0000000000..5dab84b358 --- /dev/null +++ b/.vsts-pipelines/builds/ci.yml @@ -0,0 +1,46 @@ +trigger: +- dev +- release/* + +phases: +- phase: Windows + queue: Hosted VS2017 + steps: + - checkout: self + clean: true + - script: .\build.cmd -ci + displayName: Run build.cmd + - task: PublishTestResults@2 + displayName: Publish test results + condition: always() + inputs: + testRunner: vstest + testResultsFiles: 'artifacts/logs/**/*.trx' + +- phase: Mac + queue: Hosted macOS Preview + steps: + - checkout: self + clean: true + - script: ./build.sh -ci + displayName: Run build.sh + - task: PublishTestResults@2 + displayName: Publish test results + condition: always() + inputs: + testRunner: vstest + testResultsFiles: 'artifacts/logs/**/*.trx' + +- phase: Linux + queue: Hosted Linux Preview + steps: + - checkout: self + clean: true + - script: ./build.sh -ci + displayName: Run build.sh + - task: PublishTestResults@2 + displayName: Publish test results + condition: always() + inputs: + testRunner: vstest + testResultsFiles: 'artifacts/logs/**/*.trx' From b7064c576de0ae04eb7731b59069ecab54c51a99 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 7 Jun 2018 11:28:48 -0700 Subject: [PATCH 044/316] Use templates for VSTS builds --- .vsts-pipelines/builds/ci-internal.yml | 13 ++ .vsts-pipelines/builds/ci-public.yml | 15 ++ .vsts-pipelines/builds/ci.yml | 46 ------ build/dependencies.props | 150 +++++++++--------- korebuild-lock.txt | 4 +- ...pNetCore.Mvc.Analyzers.Experimental.csproj | 2 +- 6 files changed, 106 insertions(+), 124 deletions(-) create mode 100644 .vsts-pipelines/builds/ci-internal.yml create mode 100644 .vsts-pipelines/builds/ci-public.yml delete mode 100644 .vsts-pipelines/builds/ci.yml diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml new file mode 100644 index 0000000000..d7ceb76378 --- /dev/null +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -0,0 +1,13 @@ +trigger: +- dev +- release/* + +resources: + repositories: + - repository: buildtools + type: git + name: aspnet-BuildTools + ref: refs/heads/dev + +phases: +- template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml new file mode 100644 index 0000000000..b7f25723f8 --- /dev/null +++ b/.vsts-pipelines/builds/ci-public.yml @@ -0,0 +1,15 @@ +trigger: +- dev +- release/* + +# See https://github.com/aspnet/BuildTools +resources: + repositories: + - repository: buildtools + type: github + endpoint: DotNet-Bot GitHub Connection + name: aspnet/BuildTools + ref: refs/heads/dev + +phases: +- template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci.yml b/.vsts-pipelines/builds/ci.yml deleted file mode 100644 index 5dab84b358..0000000000 --- a/.vsts-pipelines/builds/ci.yml +++ /dev/null @@ -1,46 +0,0 @@ -trigger: -- dev -- release/* - -phases: -- phase: Windows - queue: Hosted VS2017 - steps: - - checkout: self - clean: true - - script: .\build.cmd -ci - displayName: Run build.cmd - - task: PublishTestResults@2 - displayName: Publish test results - condition: always() - inputs: - testRunner: vstest - testResultsFiles: 'artifacts/logs/**/*.trx' - -- phase: Mac - queue: Hosted macOS Preview - steps: - - checkout: self - clean: true - - script: ./build.sh -ci - displayName: Run build.sh - - task: PublishTestResults@2 - displayName: Publish test results - condition: always() - inputs: - testRunner: vstest - testResultsFiles: 'artifacts/logs/**/*.trx' - -- phase: Linux - queue: Hosted Linux Preview - steps: - - checkout: self - clean: true - - script: ./build.sh -ci - displayName: Run build.sh - - task: PublishTestResults@2 - displayName: Publish test results - condition: always() - inputs: - testRunner: vstest - testResultsFiles: 'artifacts/logs/**/*.trx' diff --git a/build/dependencies.props b/build/dependencies.props index 366a541102..3b6335449f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34373 - 2.2.0-preview1-17067 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 + 2.2.0-preview1-34411 + 2.2.0-preview1-17081 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34373 + 2.2.0-preview1-34411 1.7.0 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-26531-03 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-26606-01 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 2.0.0 2.1.0 - 2.2.0-preview1-26531-03 - 2.2.0-preview1-34373 - 2.2.0-preview1-34373 + 2.2.0-preview1-26606-01 + 2.2.0-preview1-34411 + 2.2.0-preview1-34411 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26531-03 - 4.6.0-preview1-26531-03 - 4.6.0-preview1-26531-03 + 4.6.0-preview1-26605-01 + 4.6.0-preview1-26605-01 + 4.6.0-preview1-26605-01 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index b679b80427..deb7e546f0 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17075 -commithash:d9f07c7f313a0af1d49f003f5424b4dbbdd3e09f +version:2.2.0-preview1-17081 +commithash:73f09c256e2a54270951562ecc0ef4a953926c36 diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj index a124203417..9c4237c2ab 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj @@ -23,7 +23,7 @@ - + From f2eb6f8d3711ca31c77cb75368f8219192fb8889 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 6 Jun 2018 12:08:08 -0700 Subject: [PATCH 045/316] Add some tests for CodeAnalysisExtensions --- .../CodeAnalysisExtensions.cs | 59 ++++- .../Properties/AssemblyInfo.cs | 6 + .../CodeAnalysisExtensionsTest.cs | 243 ++++++++++++++++++ ...owAnonymousIsAppliedToPageHandlerMethod.cs | 2 +- ...eturnsFalseIfSymbolDoesNotHaveAttribute.cs | 19 ++ ...ibute_ReturnsTrueForAttributesOnMethods.cs | 12 + ...rnsTrueForAttributesOnOverriddenMethods.cs | 17 ++ ...sTrueForAttributesOnOverridenProperties.cs | 17 ++ ...te_ReturnsTrueForAttributesOnProperties.cs | 12 + ...urnsTrueForInterfaceContractOnAttribute.cs | 17 ++ ...ibute_ReturnsTrueIfBaseTypeHasAttribute.cs | 7 + ...Attribute_ReturnsTrueIfTypeHasAttribute.cs | 5 + ...ssignable_ReturnsFalseForDifferentTypes.cs | 10 + ...nsTrueIfAncestorTypeImplementsInterface.cs | 18 ++ ...le_ReturnsTrueIfTypeImplementsInterface.cs | 9 + ...Assignable_ReturnsTrueIfTypeIsBaseClass.cs | 10 + ...IsAssignable_ReturnsTrueIfTypesAreExact.cs | 6 + 17 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs index c473cc091d..6c191174ab 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -11,6 +11,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) { + Debug.Assert(typeSymbol != null); + Debug.Assert(attribute != null); + + if (!inherit) + { + return HasAttribute(typeSymbol, attribute); + } + foreach (var type in typeSymbol.GetTypeHierarchy()) { if (type.HasAttribute(attribute)) @@ -22,17 +30,47 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } - public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) + public static bool HasAttribute(this IMethodSymbol methodSymbol, ITypeSymbol attribute, bool inherit) { - Debug.Assert(symbol != null); + Debug.Assert(methodSymbol != null); Debug.Assert(attribute != null); - foreach (var declaredAttribute in symbol.GetAttributes()) + if (!inherit) { - if (declaredAttribute.AttributeClass == attribute) + return HasAttribute(methodSymbol, attribute); + } + + while (methodSymbol != null) + { + if (methodSymbol.HasAttribute(attribute)) { return true; } + + methodSymbol = methodSymbol.IsOverride ? methodSymbol.OverriddenMethod : null; + } + + return false; + } + + public static bool HasAttribute(this IPropertySymbol propertySymbol, ITypeSymbol attribute, bool inherit) + { + Debug.Assert(propertySymbol != null); + Debug.Assert(attribute != null); + + if (!inherit) + { + return HasAttribute(propertySymbol, attribute); + } + + while (propertySymbol != null) + { + if (propertySymbol.HasAttribute(attribute)) + { + return true; + } + + propertySymbol = propertySymbol.IsOverride ? propertySymbol.OverriddenProperty : null; } return false; @@ -67,6 +105,19 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } + private static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) + { + foreach (var declaredAttribute in symbol.GetAttributes()) + { + if (attribute.IsAssignableFrom(declaredAttribute.AttributeClass)) + { + return true; + } + } + + return false; + } + private static IEnumerable GetTypeHierarchy(this ITypeSymbol typeSymbol) { while (typeSymbol != null) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..45468cf21e --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs new file mode 100644 index 0000000000..e0e9a90d5c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs @@ -0,0 +1,243 @@ +// 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.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class CodeAnalysisExtensionsTest + { + [Fact] + public async Task HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttributeTest"); + var testMethod = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); + var testProperty = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); + + // Act + var classHasAttribute = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: false); + var methodHasAttribute = CodeAnalysisExtensions.HasAttribute(testMethod, attribute, inherit: false); + var propertyHasAttribute = CodeAnalysisExtensions.HasAttribute(testProperty, attribute, inherit: false); + + // AssertControllerAttribute + Assert.False(classHasAttribute); + Assert.False(methodHasAttribute); + Assert.False(propertyHasAttribute); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueIfTypeHasAttribute() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.ControllerAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.{nameof(HasAttribute_ReturnsTrueIfTypeHasAttribute)}"); + + // Act + var hasAttribute = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: false); + + // Assert + Assert.True(hasAttribute); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueIfBaseTypeHasAttribute() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.ControllerAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.{nameof(HasAttribute_ReturnsTrueIfBaseTypeHasAttribute)}"); + + // Act + var hasAttributeWithoutInherit = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: false); + var hasAttributeWithInherit = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: true); + + // Assert + Assert.False(hasAttributeWithoutInherit); + Assert.True(hasAttributeWithInherit); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueForInterfaceContractOnAttribute() + { + // Arrange + var compilation = await GetCompilation(); + var @interface = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IHasAttribute_ReturnsTrueForInterfaceContractOnAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeTest"); + var derivedClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeDerived"); + + // Act + var hasAttribute = CodeAnalysisExtensions.HasAttribute(testClass, @interface, inherit: true); + var hasAttributeOnDerived = CodeAnalysisExtensions.HasAttribute(testClass, @interface, inherit: true); + + // Assert + Assert.True(hasAttribute); + Assert.True(hasAttributeOnDerived); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueForAttributesOnMethods() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsTest"); + var method = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); + + // Act + var hasAttribute = CodeAnalysisExtensions.HasAttribute(method, attribute, inherit: false); + + // Assert + Assert.True(hasAttribute); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsTest"); + var method = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); + + + // Act + var hasAttributeWithoutInherit = CodeAnalysisExtensions.HasAttribute(method, attribute, inherit: false); + var hasAttributeWithInherit = CodeAnalysisExtensions.HasAttribute(method, attribute, inherit: true); + + // Assert + Assert.False(hasAttributeWithoutInherit); + Assert.True(hasAttributeWithInherit); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueForAttributesOnProperties() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnPropertiesAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnProperties"); + var property = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); + + // Act + var hasAttribute = CodeAnalysisExtensions.HasAttribute(property, attribute, inherit: false); + + // Assert + Assert.True(hasAttribute); + } + + [Fact] + public async Task HasAttribute_ReturnsTrueForAttributesOnOverridenProperties() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenProperties"); + var property = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); + + // Act + var hasAttributeWithoutInherit = CodeAnalysisExtensions.HasAttribute(property, attribute, inherit: false); + var hasAttributeWithInherit = CodeAnalysisExtensions.HasAttribute(property, attribute, inherit: true); + + // Assert + Assert.False(hasAttributeWithoutInherit); + Assert.True(hasAttributeWithInherit); + } + + [Fact] + public async Task IsAssignable_ReturnsFalseForDifferentTypes() + { + // Arrange + var compilation = await GetCompilation(); + var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); + var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesB"); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + + // Assert + Assert.False(isAssignableFrom); + } + + [Fact] + public async Task IsAssignable_ReturnsFalseIfTypeDoesNotImplementInterface() + { + // Arrange + var compilation = await GetCompilation(nameof(IsAssignable_ReturnsFalseForDifferentTypes)); + var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); + var target = compilation.GetTypeByMetadataName($"System.IDisposable"); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + + // Assert + Assert.False(isAssignableFrom); + } + + [Fact] + public async Task IsAssignable_ReturnsTrueIfTypesAreExact() + { + // Arrange + var compilation = await GetCompilation(); + var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); + var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + + // Assert + Assert.True(isAssignableFrom); + } + + [Fact] + public async Task IsAssignable_ReturnsTrueIfTypeImplementsInterface() + { + // Arrange + var compilation = await GetCompilation(); + var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterface"); + var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterfaceTest"); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + var isAssignableFromDerived = CodeAnalysisExtensions.IsAssignableFrom(target, source); + + // Assert + Assert.True(isAssignableFrom); + Assert.False(isAssignableFromDerived); // Inverse shouldn't be true + } + + [Fact] + public async Task IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface() + { + // Arrange + var compilation = await GetCompilation(); + var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface"); + var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceTest"); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + var isAssignableFromDerived = CodeAnalysisExtensions.IsAssignableFrom(target, source); + + // Assert + Assert.True(isAssignableFrom); + Assert.False(isAssignableFromDerived); // Inverse shouldn't be true + } + + private Task GetCompilation([CallerMemberName] string testMethod = "") + { + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs index 45ef30ce92..c070495950 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs @@ -1,7 +1,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Mvc.Analyzers.Test +namespace Microsoft.AspNetCore.Mvc.Analyzers { public class DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod : PageModel { diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs new file mode 100644 index 0000000000..db5c15882f --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs @@ -0,0 +1,19 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttribute : Attribute { } + + [Controller] + public class HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttributeTest + { + [NonAction] + public void SomeMethod() + { + + } + + [BindProperty] + public string SomeProperty { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs new file mode 100644 index 0000000000..4dd452410e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs @@ -0,0 +1,12 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class HasAttribute_ReturnsTrueForAttributesOnMethodsAttribute : Attribute { } + + public class HasAttribute_ReturnsTrueForAttributesOnMethodsTest + { + [HasAttribute_ReturnsTrueForAttributesOnMethodsAttribute] + public void SomeMethod() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs new file mode 100644 index 0000000000..64ef7b87b9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs @@ -0,0 +1,17 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsAttribute : Attribute { } + + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsBase + { + [HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsAttribute] + public virtual void SomeMethod() { } + } + + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsTest : HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsBase + { + public override void SomeMethod() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs new file mode 100644 index 0000000000..872f35fbfb --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs @@ -0,0 +1,17 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesAttribute : Attribute { } + + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesBase + { + [HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesAttribute] + public virtual string SomeProperty { get; set; } + } + + public class HasAttribute_ReturnsTrueForAttributesOnOverriddenProperties : HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesBase + { + public override string SomeProperty { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs new file mode 100644 index 0000000000..7b36527ddb --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs @@ -0,0 +1,12 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class HasAttribute_ReturnsTrueForAttributesOnPropertiesAttribute : Attribute { } + + public class HasAttribute_ReturnsTrueForAttributesOnProperties + { + [HasAttribute_ReturnsTrueForAttributesOnPropertiesAttribute] + public string SomeProperty { get; set; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs new file mode 100644 index 0000000000..25a966df8e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs @@ -0,0 +1,17 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public interface IHasAttribute_ReturnsTrueForInterfaceContractOnAttribute { } + + public class HasAttribute_ReturnsTrueForInterfaceContractOnAttribute : Attribute, IHasAttribute_ReturnsTrueForInterfaceContractOnAttribute { } + + [HasAttribute_ReturnsTrueForInterfaceContractOnAttribute] + public class HasAttribute_ReturnsTrueForInterfaceContractOnAttributeTest + { + } + + public class HasAttribute_ReturnsTrueForInterfaceContractOnAttributeDerived : HasAttribute_ReturnsTrueForInterfaceContractOnAttributeTest + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs new file mode 100644 index 0000000000..39851099c2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [Controller] + public class HasAttribute_ReturnsTrueIfBaseTypeHasAttributeBase { } + + public class HasAttribute_ReturnsTrueIfBaseTypeHasAttribute : HasAttribute_ReturnsTrueIfBaseTypeHasAttributeBase { } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs new file mode 100644 index 0000000000..31c6cf70cc --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs @@ -0,0 +1,5 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [Controller] + public class HasAttribute_ReturnsTrueIfTypeHasAttribute { } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs new file mode 100644 index 0000000000..ceef09cde4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class IsAssignable_ReturnsFalseForDifferentTypesA + { + } + + public class IsAssignable_ReturnsFalseForDifferentTypesB + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs new file mode 100644 index 0000000000..60d8aecb48 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public interface IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface + { + } + + public class IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceA : IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface + { + } + + public class IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceB : IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceA + { + } + + public class IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceTest : IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceB + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs new file mode 100644 index 0000000000..a60d9fa56d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public interface IsAssignable_ReturnsTrueIfTypeImplementsInterface + { + } + + public class IsAssignable_ReturnsTrueIfTypeImplementsInterfaceTest : IsAssignable_ReturnsTrueIfTypeImplementsInterface { } + +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs new file mode 100644 index 0000000000..8ed1087a79 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class IsAssignable_ReturnsTrueIfTypeIsBaseClassBase + { + } + + public class IsAssignable_ReturnsTrueIfTypeIsBaseClass : IsAssignable_ReturnsTrueIfTypeIsBaseClassBase + { + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs new file mode 100644 index 0000000000..e47fa67dcc --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs @@ -0,0 +1,6 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class IsAssignable_ReturnsTrueIfTypesAreExact + { + } +} From c29527f992c565f7792d50b3bfffc8d2bbe0eb2a Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 6 Jun 2018 18:51:05 -0700 Subject: [PATCH 046/316] Add some assertions for controllers and controller actions --- .../MvcFacts.cs | 137 ++++++++++ .../MvcFactsTest.cs | 235 ++++++++++++++++++ .../MvcFactsTest/IsControllerActionTests.cs | 75 ++++++ .../MvcFactsTest/IsControllerTests.cs | 44 ++++ 4 files changed, 491 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs new file mode 100644 index 0000000000..90651247c4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs @@ -0,0 +1,137 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal static class MvcFacts + { + public static bool IsController(INamedTypeSymbol type, INamedTypeSymbol controllerAttribute, INamedTypeSymbol nonControllerAttribute) + { + Debug.Assert(type != null); + Debug.Assert(controllerAttribute != null); + Debug.Assert(nonControllerAttribute != null); + + if (type.TypeKind != TypeKind.Class) + { + return false; + } + + if (type.IsAbstract) + { + return false; + } + + // We only consider public top-level classes as controllers. + if (type.DeclaredAccessibility != Accessibility.Public) + { + return false; + } + + if (type.ContainingType != null) + { + return false; + } + + if (type.IsGenericType || type.IsUnboundGenericType) + { + return false; + } + + if (type.HasAttribute(nonControllerAttribute, inherit: true)) + { + return false; + } + + if (!type.Name.EndsWith("Controller", StringComparison.OrdinalIgnoreCase) && + !type.HasAttribute(controllerAttribute, inherit: true)) + { + return false; + } + + return true; + } + + public static bool IsControllerAction(IMethodSymbol method, INamedTypeSymbol nonActionAttribute, IMethodSymbol disposableDispose) + { + Debug.Assert(method != null); + Debug.Assert(nonActionAttribute != null); + + if (method.MethodKind != MethodKind.Ordinary) + { + return false; + } + + if (method.HasAttribute(nonActionAttribute, inherit: true)) + { + return false; + } + + // Overridden methods from Object class, e.g. Equals(Object), GetHashCode(), etc., are not valid. + if (GetDeclaringType(method).SpecialType == SpecialType.System_Object) + { + return false; + } + + if (IsIDisposableDispose(method, disposableDispose)) + { + return false; + } + + if (method.IsStatic) + { + return false; + } + + if (method.IsAbstract) + { + return false; + } + + if (method.IsGenericMethod) + { + return false; + } + + return method.DeclaredAccessibility == Accessibility.Public; + } + + private static INamedTypeSymbol GetDeclaringType(IMethodSymbol method) + { + while (method.IsOverride) + { + method = method.OverriddenMethod; + } + + return method.ContainingType; + } + + private static bool IsIDisposableDispose(IMethodSymbol method, IMethodSymbol disposableDispose) + { + if (method.Name != disposableDispose.Name) + { + return false; + } + + if (method.Parameters.Length != disposableDispose.Parameters.Length) + { + return false; + } + + // Explicit implementation + for (var i = 0; i < method.ExplicitInterfaceImplementations.Length; i++) + { + if (method.ExplicitInterfaceImplementations[i].ContainingType.SpecialType == SpecialType.System_IDisposable) + { + return true; + } + } + + var implementedMethod = method.ContainingType.FindImplementationForInterfaceMember(disposableDispose); + return implementedMethod == method; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs new file mode 100644 index 0000000000..1d81f7e41d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs @@ -0,0 +1,235 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class MvcFactsTest + { + private static readonly string ControllerAttribute = typeof(ControllerAttribute).FullName; + private static readonly string NonControllerAttribute = typeof(NonControllerAttribute).FullName; + private static readonly string NonActionAttribute = typeof(NonActionAttribute).FullName; + private static readonly Type TestIsControllerActionType = typeof(TestIsControllerAction); + + #region IsController + [Fact] + public Task IsController_ReturnsFalseForInterfaces() => IsControllerReturnsFalse(typeof(ITestController)); + + [Fact] + public Task IsController_ReturnsFalseForAbstractTypes() => IsControllerReturnsFalse(typeof(AbstractController)); + + [Fact] + public Task IsController_ReturnsFalseForValueType() => IsControllerReturnsFalse(typeof(ValueTypeController)); + + + [Fact] + public Task IsController_ReturnsFalseForGenericType() => IsControllerReturnsFalse(typeof(OpenGenericController<>)); + + [Fact] + public Task IsController_ReturnsFalseForPocoType() => IsControllerReturnsFalse(typeof(PocoType)); + + [Fact] + public Task IsController_ReturnsFalseForTypeDerivedFromPocoType() => IsControllerReturnsFalse(typeof(DerivedPocoType)); + + [Fact] + public Task IsController_ReturnsTrueForTypeDerivingFromController() => IsControllerReturnsTrue(typeof(TypeDerivingFromController)); + + [Fact] + public Task IsController_ReturnsTrueForTypeDerivingFromControllerBase() => IsControllerReturnsTrue(typeof(TypeDerivingFromControllerBase)); + + [Fact] + public Task IsController_ReturnsTrueForTypeDerivingFromController_WithoutSuffix() => IsControllerReturnsTrue(typeof(NoSuffix)); + + [Fact] + public Task IsController_ReturnsTrueForTypeWithSuffix_ThatIsNotDerivedFromController() => IsControllerReturnsTrue(typeof(PocoController)); + + [Fact] + public Task IsController_ReturnsTrueForTypeWithoutSuffix_WithControllerAttribute() => IsControllerReturnsTrue(typeof(CustomBase)); + + [Fact] + public Task IsController_ReturnsTrueForTypeDerivingFromCustomBaseThatHasControllerAttribute() => IsControllerReturnsTrue(typeof(ChildOfCustomBase)); + + [Fact] + public Task IsController_ReturnsFalseForTypeWithNonControllerAttribute() => IsControllerReturnsFalse(typeof(BaseNonController)); + + [Fact] + public Task IsController_ReturnsFalseForTypesDerivingFromTypeWithNonControllerAttribute() => IsControllerReturnsFalse(typeof(BasePocoNonControllerChildController)); + + [Fact] + public Task IsController_ReturnsFalseForTypesDerivingFromTypeWithNonControllerAttributeWithControllerAttribute() => + IsControllerReturnsFalse(typeof(ControllerAttributeDerivingFromNonController)); + + private async Task IsControllerReturnsFalse(Type type) + { + var compilation = await GetIsControllerCompilation(); + var controllerAttribute = compilation.GetTypeByMetadataName(ControllerAttribute); + var nonControllerAttribute = compilation.GetTypeByMetadataName(NonControllerAttribute); + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + + // Act + var isController = MvcFacts.IsController(typeSymbol, controllerAttribute, nonControllerAttribute); + + // Assert + Assert.False(isController); + } + + private async Task IsControllerReturnsTrue(Type type) + { + var compilation = await GetIsControllerCompilation(); + var controllerAttribute = compilation.GetTypeByMetadataName(ControllerAttribute); + var nonControllerAttribute = compilation.GetTypeByMetadataName(NonControllerAttribute); + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + + // Act + var isController = MvcFacts.IsController(typeSymbol, controllerAttribute, nonControllerAttribute); + + // Assert + Assert.True(isController); + } + + #endregion + + #region IsControllerAction + [Fact] + public Task IsAction_ReturnsFalseForConstructor() => IsActionReturnsFalse(TestIsControllerActionType, ".ctor"); + + [Fact] + public Task IsAction_ReturnsFalseForStaticConstructor() => IsActionReturnsFalse(TestIsControllerActionType, ".cctor"); + + [Fact] + public Task IsAction_ReturnsFalseForPrivateMethod() => IsActionReturnsFalse(TestIsControllerActionType, "PrivateMethod"); + + [Fact] + public Task IsAction_ReturnsFalseForProtectedMethod() => IsActionReturnsFalse(TestIsControllerActionType, "ProtectedMethod"); + + [Fact] + public Task IsAction_ReturnsFalseForInternalMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.InternalMethod)); + + [Fact] + public Task IsAction_ReturnsFalseForGenericMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.GenericMethod)); + + [Fact] + public Task IsAction_ReturnsFalseForStaticMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.StaticMethod)); + + [Fact] + public Task IsAction_ReturnsFalseForNonActionMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.NonAction)); + + [Fact] + public Task IsAction_ReturnsFalseForOverriddenNonActionMethod() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.NonActionBase)); + + [Fact] + public Task IsAction_ReturnsFalseForDisposableDispose() => IsActionReturnsFalse(TestIsControllerActionType, nameof(TestIsControllerAction.Dispose)); + + [Fact] + public Task IsAction_ReturnsFalseForExplicitDisposableDispose() => IsActionReturnsFalse(typeof(ExplicitIDisposable), "System.IDisposable.Dispose"); + + [Fact] + public Task IsAction_ReturnsFalseForAbstractMethods() => IsActionReturnsFalse(typeof(TestIsControllerActionBase), nameof(TestIsControllerActionBase.AbstractMethod)); + + [Fact] + public Task IsAction_ReturnsFalseForObjectEquals() => IsActionReturnsFalse(typeof(object), nameof(object.Equals)); + + [Fact] + public Task IsAction_ReturnsFalseForObjectHashCode() => IsActionReturnsFalse(typeof(object), nameof(object.GetHashCode)); + + [Fact] + public Task IsAction_ReturnsFalseForObjectToString() => IsActionReturnsFalse(typeof(object), nameof(object.ToString)); + + [Fact] + public Task IsAction_ReturnsFalseForOverriddenObjectEquals() => + IsActionReturnsFalse(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.Equals)); + + [Fact] + public Task IsAction_ReturnsFalseForOverriddenObjectHashCode() => + IsActionReturnsFalse(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.GetHashCode)); + + private async Task IsActionReturnsFalse(Type type, string methodName) + { + var compilation = await GetIsControllerActionCompilation(); + var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); + var disposableDispose = GetDisposableDispose(compilation); + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + var method = (IMethodSymbol)typeSymbol.GetMembers(methodName).First(); + + // Act + var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); + + // Assert + Assert.False(isControllerAction); + } + + [Fact] + public Task IsAction_ReturnsTrueForNewMethodsOfObject() => IsActionReturnsTrue(typeof(OverridesObjectMethods), nameof(OverridesObjectMethods.ToString)); + + [Fact] + public Task IsAction_ReturnsTrueForNotDisposableDispose() => IsActionReturnsTrue(typeof(NotDisposable), nameof(NotDisposable.Dispose)); + + [Fact] + public Task IsAction_ReturnsTrueForNotDisposableDisposeOnTypeWithExplicitImplementation() => + IsActionReturnsTrue(typeof(NotDisposableWithExplicitImplementation), nameof(NotDisposableWithExplicitImplementation.Dispose)); + + [Fact] + public Task IsAction_ReturnsTrueForOrdinaryAction() => IsActionReturnsTrue(TestIsControllerActionType, nameof(TestIsControllerAction.Ordinary)); + + [Fact] + public Task IsAction_ReturnsTrueForOverriddenMethod() => IsActionReturnsTrue(TestIsControllerActionType, nameof(TestIsControllerAction.AbstractMethod)); + + [Fact] + public async Task IsAction_ReturnsTrueForNotDisposableDisposeOnTypeWithImplicitImplementation() + { + var compilation = await GetIsControllerActionCompilation(); + var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); + var disposableDispose = GetDisposableDispose(compilation); + var typeSymbol = compilation.GetTypeByMetadataName(typeof(NotDisposableWithDisposeThatIsNotInterfaceContract).FullName); + var method = typeSymbol.GetMembers(nameof(IDisposable.Dispose)).OfType().First(f => !f.ReturnsVoid); + + // Act + var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); + + // Assert + Assert.True(isControllerAction); + } + + private async Task IsActionReturnsTrue(Type type, string methodName) + { + var compilation = await GetIsControllerActionCompilation(); + var nonActionAttribute = compilation.GetTypeByMetadataName(NonActionAttribute); + var disposableDispose = GetDisposableDispose(compilation); + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + var method = (IMethodSymbol)typeSymbol.GetMembers(methodName).First(); + + // Act + var isControllerAction = MvcFacts.IsControllerAction(method, nonActionAttribute, disposableDispose); + + // Assert + Assert.True(isControllerAction); + } + + private IMethodSymbol GetDisposableDispose(Compilation compilation) + { + var type = compilation.GetSpecialType(SpecialType.System_IDisposable); + return (IMethodSymbol)type.GetMembers(nameof(IDisposable.Dispose)).First(); + } + #endregion + + + private Task GetIsControllerCompilation() => GetCompilation("IsControllerTests"); + + private Task GetIsControllerActionCompilation() => GetCompilation("IsControllerActionTests"); + + private Task GetCompilation(string test) + { + var testSource = MvcTestSource.Read(GetType().Name, test); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs new file mode 100644 index 0000000000..607715bcb7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs @@ -0,0 +1,75 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public abstract class TestIsControllerActionBase : ControllerBase + { + public abstract IActionResult AbstractMethod(); + + public virtual IActionResult VirtualMethod() => null; + + public virtual IActionResult MethodInBase() => null; + + [NonAction] + public virtual IActionResult NonActionBase() => null; + } + + public class TestIsControllerAction : TestIsControllerActionBase, IDisposable + { + static TestIsControllerAction() { } + + public override IActionResult AbstractMethod() => null; + + private IActionResult PrivateMethod() => null; + + protected IActionResult ProtectedMethod() => null; + + internal IActionResult InternalMethod() => null; + + public IActionResult GenericMethod() => null; + + public static IActionResult StaticMethod() => null; + + [NonAction] + public IActionResult NonAction() => null; + + public override IActionResult NonActionBase() => null; + + public IActionResult Ordinary() => null; + + public void Dispose() { } + } + + public class OverridesObjectMethods : ControllerBase + { + public override bool Equals(object obj) => false; + + public override int GetHashCode() => 0; + + public new string ToString() => null; + } + + public class ExplicitIDisposable : ControllerBase, IDisposable + { + void IDisposable.Dispose() { } + } + + public class NotDisposable + { + public IActionResult Dispose() => null; + } + + public class NotDisposableWithExplicitImplementation : IDisposable + { + public IActionResult Dispose() => null; + + void IDisposable.Dispose() { } + } + + public class NotDisposableWithDisposeThatIsNotInterfaceContract : IDisposable + { + public IActionResult Dispose(int id) => null; + + void IDisposable.Dispose() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs new file mode 100644 index 0000000000..1837ac712b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs @@ -0,0 +1,44 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public interface ITestController { } + + public abstract class AbstractController : Controller { } + + public class DerivedAbstractController : AbstractController { } + + public struct ValueTypeController { } + + public class OpenGenericController : Controller { } + + public class PocoType { } + + public class DerivedPocoType : PocoType { } + + public class TypeDerivingFromController : Controller { } + + public class TypeDerivingFromControllerBase : ControllerBase { } + + public abstract class NoControllerAttributeBaseController { } + + public class NoSuffixNoControllerAttribute : NoControllerAttributeBaseController { } + + public class DerivedGenericController : OpenGenericController { } + + public class NoSuffix : Controller { } + + public class PocoController { } + + [Controller] + public class CustomBase { } + + [Controller] + public class ChildOfCustomBase : CustomBase { } + + [NonController] + public class BaseNonController { } + + [Controller] + public class ControllerAttributeDerivingFromNonController : BaseNonController { } + + public class BasePocoNonControllerChildController : BaseNonController { } +} From 203258d00eb0880bf211e71c7b149b083b8efb14 Mon Sep 17 00:00:00 2001 From: Brecht Carlier Date: Sat, 19 May 2018 13:52:30 +0200 Subject: [PATCH 047/316] Update AnchorTagHelper.cs Fixed incorrect documentation --- src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs index 175523d2fb..5eefa99543 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// The name of the area. ///
/// - /// Must be null if or is non-null. + /// Must be null if is non-null. /// [HtmlAttributeName(AreaAttributeName)] public string Area { get; set; } @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// /// /// Must be null if or , - /// or is non-null. + /// is non-null. /// [HtmlAttributeName(PageAttributeName)] public string Page { get; set; } @@ -280,4 +280,4 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers output.MergeAttributes(tagBuilder); } } -} \ No newline at end of file +} From df6b798117d67a6949e416e6fdb9fc17ddf8ce24 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 7 Jun 2018 13:07:11 -0700 Subject: [PATCH 048/316] Remove `xUnit1026` workaround in test projects - contained a typo (colon versus semicolon) and just doesn't matter --- test/Directory.Build.props | 1 - 1 file changed, 1 deletion(-) diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 7ba9394b16..96765456ba 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -10,7 +10,6 @@ false - xUnit1026:$(WarningsNotAsErrors) $(MSBuildThisFileDirectory)MvcTests.ruleset From dc5bfd1b0b0bfff2a6750712d3b916f8850bb3cd Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 11 Jun 2018 10:13:54 -0700 Subject: [PATCH 049/316] Fixed typo in issue template --- .github/ISSUE_TEMPLATE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE b/.github/ISSUE_TEMPLATE index c4282ab656..4fef458138 100644 --- a/.github/ISSUE_TEMPLATE +++ b/.github/ISSUE_TEMPLATE @@ -1,7 +1,7 @@ #### Is this a Bug or Feature request?: -#### Steps to reproduce (preferrably a link to a GitHub repo with a repro project): +#### Steps to reproduce (preferably a link to a GitHub repo with a repro project): #### Description of the problem: From 6c2ef122f83741d1b2542fedebe83a68fd374f94 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 7 Jun 2018 12:06:27 -0700 Subject: [PATCH 050/316] Add support for conventions in DefaultApiDescriptionProvider --- build/dependencies.props | 140 ++-- korebuild-lock.txt | 4 +- .../ApiResponseTypeProvider.cs | 334 +++++++++ .../DefaultApiDescriptionProvider.cs | 168 +---- .../ApiConventionAttribute.cs | 26 + .../ApplicationModels/ActionModel.cs | 6 + .../DefaultApiConventions.cs | 31 + .../ApiBehaviorApplicationModelProvider.cs | 18 + .../Properties/Resources.Designer.cs | 18 +- .../Resources.resx | 3 + .../ApiResponseTypeProviderTest.cs | 631 ++++++++++++++++++ ...ApiBehaviorApplicationModelProviderTest.cs | 54 ++ 12 files changed, 1195 insertions(+), 238 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs diff --git a/build/dependencies.props b/build/dependencies.props index 3b6335449f..d3cfd72116 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,84 +5,84 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34411 - 2.2.0-preview1-17081 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 + 2.2.0-preview1-34425 + 2.2.0-preview1-17082 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34411 + 2.2.0-preview1-34425 1.7.0 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 2.2.0-preview1-26606-01 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 2.0.0 2.1.0 2.2.0-preview1-26606-01 - 2.2.0-preview1-34411 - 2.2.0-preview1-34411 + 2.2.0-preview1-34425 + 2.2.0-preview1-34425 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index deb7e546f0..284486474a 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17081 -commithash:73f09c256e2a54270951562ecc0ef4a953926c36 +version:2.2.0-preview1-17082 +commithash:13b85a32c7aa9d62f6f3cc211c5c7c566d16b3dd diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs new file mode 100644 index 0000000000..6414542f39 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -0,0 +1,334 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + internal class ApiResponseTypeProvider + { + private readonly IModelMetadataProvider _modelMetadataProvider; + private readonly IActionResultTypeMapper _mapper; + private readonly MvcOptions _mvcOptions; + + public ApiResponseTypeProvider( + IModelMetadataProvider modelMetadataProvider, + IActionResultTypeMapper mapper, + MvcOptions mvcOptions) + { + _modelMetadataProvider = modelMetadataProvider; + _mapper = mapper; + _mvcOptions = mvcOptions; + } + + public IList GetApiResponseTypes(ControllerActionDescriptor action) + { + // We only provide response info if we can figure out a type that is a user-data type. + // Void /Task object/IActionResult will result in no data. + var declaredReturnType = GetDeclaredReturnType(action); + + var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); + + var responseMetadataAttributes = GetResponseMetadataAttributes(action); + if (responseMetadataAttributes.Length == 0) + { + // Action does not have any conventions. Look for conventions on the type. + responseMetadataAttributes = GetResponseMetadataAttributesFromConventions(action); + } + + var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType); + return apiResponseTypes; + } + + private IApiResponseMetadataProvider[] GetResponseMetadataAttributesFromConventions(ControllerActionDescriptor action) + { + if (action.FilterDescriptors == null) + { + return Array.Empty(); + } + + foreach (var filterDescriptor in action.FilterDescriptors) + { + if (!(filterDescriptor.Filter is ApiConventionAttribute apiConventionAttribute)) + { + continue; + } + + var method = GetConventionMethod(action.MethodInfo, apiConventionAttribute.ConventionType); + if (method != null) + { + return method.GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); + } + } + + return Array.Empty(); + } + + private MethodInfo GetConventionMethod(MethodInfo methodInfo, Type conventions) + { + var conventionMethods = conventions.GetMethods(BindingFlags.Public | BindingFlags.Static); + for (var i = 0; i < conventionMethods.Length; i++) + { + if (IsMatch(methodInfo, conventionMethods[i])) + { + return conventionMethods[i]; + } + } + + return null; + } + + private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ControllerActionDescriptor action) + { + if (action.FilterDescriptors == null) + { + return Array.Empty(); + } + + // This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory + // while searching for a filter that implements IApiResponseMetadataProvider. + // + // The workaround for that is to implement the metadata interface on the IFilterFactory. + return action.FilterDescriptors + .Select(fd => fd.Filter) + .OfType() + .ToArray(); + } + + private IList GetApiResponseTypes( + IApiResponseMetadataProvider[] responseMetadataAttributes, + Type type) + { + var results = new List(); + + // Build list of all possible return types (and status codes) for an action. + var objectTypes = new Dictionary(); + + // Get the content type that the action explicitly set to support. + // Walk through all 'filter' attributes in order, and allow each one to see or override + // the results of the previous ones. This is similar to the execution path for content-negotiation. + var contentTypes = new MediaTypeCollection(); + if (responseMetadataAttributes != null) + { + foreach (var metadataAttribute in responseMetadataAttributes) + { + metadataAttribute.SetContentTypes(contentTypes); + + if (metadataAttribute.Type != null) + { + objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; + } + } + } + + // Set the default status only when no status has already been set explicitly + if (objectTypes.Count == 0 && type != null) + { + objectTypes[StatusCodes.Status200OK] = type; + } + + if (contentTypes.Count == 0) + { + contentTypes.Add((string)null); + } + + var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); + + foreach (var objectType in objectTypes) + { + if (objectType.Value == null || objectType.Value == typeof(void)) + { + results.Add(new ApiResponseType() + { + StatusCode = objectType.Key, + Type = objectType.Value + }); + + continue; + } + + var apiResponseType = new ApiResponseType() + { + Type = objectType.Value, + StatusCode = objectType.Key, + ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value) + }; + + foreach (var contentType in contentTypes) + { + foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders) + { + var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( + contentType, + objectType.Value); + + if (formatterSupportedContentTypes == null) + { + continue; + } + + foreach (var formatterSupportedContentType in formatterSupportedContentTypes) + { + apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() + { + Formatter = (IOutputFormatter)responseTypeMetadataProvider, + MediaType = formatterSupportedContentType, + }); + } + } + } + + results.Add(apiResponseType); + } + + return results; + } + + private Type GetDeclaredReturnType(ControllerActionDescriptor action) + { + var declaredReturnType = action.MethodInfo.ReturnType; + if (declaredReturnType == typeof(void) || + declaredReturnType == typeof(Task)) + { + return typeof(void); + } + + // Unwrap the type if it's a Task. The Task (non-generic) case was already handled. + Type unwrappedType = declaredReturnType; + if (declaredReturnType.IsGenericType && + declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>)) + { + unwrappedType = declaredReturnType.GetGenericArguments()[0]; + } + + // If the method is declared to return IActionResult or a derived class, that information + // isn't valuable to the formatter. + if (typeof(IActionResult).IsAssignableFrom(unwrappedType)) + { + return null; + } + + // If we get here, the type should be a user-defined data type or an envelope type + // like ActionResult. The mapper service will unwrap envelopes. + unwrappedType = _mapper.GetResultDataType(unwrappedType); + return unwrappedType; + } + + private Type GetRuntimeReturnType(Type declaredReturnType) + { + // If we get here, then a filter didn't give us an answer, so we need to figure out if we + // want to use the declared return type. + // + // We've already excluded Task, void, and IActionResult at this point. + // + // If the action might return any object, then assume we don't know anything about it. + if (declaredReturnType == typeof(object)) + { + return null; + } + + return declaredReturnType; + } + + internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod) + { + if (!IsMethodNameMatch(methodInfo.Name, conventionMethod.Name)) + { + return false; + } + + var methodParameters = methodInfo.GetParameters(); + var conventionMethodParameters = conventionMethod.GetParameters(); + if (conventionMethodParameters.Length != methodParameters.Length) + { + return false; + } + + for (var i = 0; i < conventionMethodParameters.Length; i++) + { + if (conventionMethodParameters[i].ParameterType.IsGenericParameter) + { + // Use TModel as wildcard + continue; + } + else if (!IsParameterNameMatch(methodParameters[i].Name, conventionMethodParameters[i].Name) || + !IsParameterTypeMatch(methodParameters[i].ParameterType, conventionMethodParameters[i].ParameterType)) + { + return false; + } + } + + return true; + } + + internal static bool IsMethodNameMatch(string name, string conventionName) + { + if (!name.StartsWith(conventionName, StringComparison.Ordinal)) + { + return false; + } + + if (name.Length == conventionName.Length) + { + return true; + } + + return char.IsUpper(name[conventionName.Length]); + } + + internal static bool IsParameterNameMatch(string name, string conventionName) + { + // Leading underscores could be used to allow multiple parameter names with the same suffix e.g. GetPersonAddress(int personId, int addressId) + // A common convention that allows targeting these category of methods would look like Get(int id, int _id) + conventionName = conventionName.Trim('_'); + + // name = id, conventionName = id + if (string.Equals(name, conventionName, StringComparison.Ordinal)) + { + return true; + } + + if (name.Length <= conventionName.Length) + { + return false; + } + + // name = personId, conventionName = id + var index = name.Length - conventionName.Length - 1; + if (!char.IsLower(name[index])) + { + return false; + } + + index++; + if (name[index] != char.ToUpper(conventionName[0])) + { + return false; + } + + index++; + return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; + } + + internal static bool IsParameterTypeMatch(Type parameterType, Type conventionParameterType) + { + if (conventionParameterType == typeof(object)) + { + return true; + } + + return conventionParameterType.IsAssignableFrom(parameterType); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 5da3f8a1cb..2b89a26e2c 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { private readonly MvcOptions _mvcOptions; private readonly IActionResultTypeMapper _mapper; + private readonly ApiResponseTypeProvider _responseTypeProvider; private readonly IInlineConstraintResolver _constraintResolver; private readonly IModelMetadataProvider _modelMetadataProvider; @@ -42,10 +43,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer IOptions optionsAccessor, IInlineConstraintResolver constraintResolver, IModelMetadataProvider modelMetadataProvider) + : this(optionsAccessor, constraintResolver, modelMetadataProvider, null) { - _mvcOptions = optionsAccessor.Value; - _constraintResolver = constraintResolver; - _modelMetadataProvider = modelMetadataProvider; } /// @@ -66,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer _constraintResolver = constraintResolver; _modelMetadataProvider = modelMetadataProvider; _mapper = mapper; + _responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions); } /// @@ -127,15 +127,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } var requestMetadataAttributes = GetRequestMetadataAttributes(action); - var responseMetadataAttributes = GetResponseMetadataAttributes(action); - // We only provide response info if we can figure out a type that is a user-data type. - // Void /Task object/IActionResult will result in no data. - var declaredReturnType = GetDeclaredReturnType(action); - - var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); - - var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType); + var apiResponseTypes = _responseTypeProvider.GetApiResponseTypes(action); foreach (var apiResponseType in apiResponseTypes) { apiDescription.SupportedResponseTypes.Add(apiResponseType); @@ -406,142 +399,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return contentTypes; } - private IReadOnlyList GetApiResponseTypes( - IApiResponseMetadataProvider[] responseMetadataAttributes, - Type type) - { - var results = new List(); - - // Build list of all possible return types (and status codes) for an action. - var objectTypes = new Dictionary(); - - // Get the content type that the action explicitly set to support. - // Walk through all 'filter' attributes in order, and allow each one to see or override - // the results of the previous ones. This is similar to the execution path for content-negotiation. - var contentTypes = new MediaTypeCollection(); - if (responseMetadataAttributes != null) - { - foreach (var metadataAttribute in responseMetadataAttributes) - { - metadataAttribute.SetContentTypes(contentTypes); - - if (metadataAttribute.Type != null) - { - objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; - } - } - } - - // Set the default status only when no status has already been set explicitly - if (objectTypes.Count == 0 - && type != null) - { - objectTypes[StatusCodes.Status200OK] = type; - } - - if (contentTypes.Count == 0) - { - contentTypes.Add((string)null); - } - - var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); - - foreach (var objectType in objectTypes) - { - if (objectType.Value == typeof(void)) - { - results.Add(new ApiResponseType() - { - StatusCode = objectType.Key, - Type = objectType.Value - }); - - continue; - } - - var apiResponseType = new ApiResponseType() - { - Type = objectType.Value, - StatusCode = objectType.Key, - ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value) - }; - - foreach (var contentType in contentTypes) - { - foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders) - { - var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( - contentType, - objectType.Value); - - if (formatterSupportedContentTypes == null) - { - continue; - } - - foreach (var formatterSupportedContentType in formatterSupportedContentTypes) - { - apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() - { - Formatter = (IOutputFormatter)responseTypeMetadataProvider, - MediaType = formatterSupportedContentType, - }); - } - } - } - - results.Add(apiResponseType); - } - - return results; - } - - private Type GetDeclaredReturnType(ControllerActionDescriptor action) - { - var declaredReturnType = action.MethodInfo.ReturnType; - if (declaredReturnType == typeof(void) || - declaredReturnType == typeof(Task)) - { - return typeof(void); - } - - // Unwrap the type if it's a Task. The Task (non-generic) case was already handled. - Type unwrappedType = declaredReturnType; - if (declaredReturnType.IsGenericType && - declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>)) - { - unwrappedType = declaredReturnType.GetGenericArguments()[0]; - } - - // If the method is declared to return IActionResult or a derived class, that information - // isn't valuable to the formatter. - if (typeof(IActionResult).IsAssignableFrom(unwrappedType)) - { - return null; - } - - // If we get here, the type should be a user-defined data type or an envelope type - // like ActionResult. The mapper service will unwrap envelopes. - unwrappedType = _mapper.GetResultDataType(unwrappedType); - return unwrappedType; - } - - private Type GetRuntimeReturnType(Type declaredReturnType) - { - // If we get here, then a filter didn't give us an answer, so we need to figure out if we - // want to use the declared return type. - // - // We've already excluded Task, void, and IActionResult at this point. - // - // If the action might return any object, then assume we don't know anything about it. - if (declaredReturnType == typeof(object)) - { - return null; - } - - return declaredReturnType; - } - private IApiRequestMetadataProvider[] GetRequestMetadataAttributes(ControllerActionDescriptor action) { if (action.FilterDescriptors == null) @@ -559,23 +416,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer .ToArray(); } - private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ControllerActionDescriptor action) - { - if (action.FilterDescriptors == null) - { - return null; - } - - // This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory - // while searching for a filter that implements IApiResponseMetadataProvider. - // - // The workaround for that is to implement the metadata interface on the IFilterFactory. - return action.FilterDescriptors - .Select(fd => fd.Filter) - .OfType() - .ToArray(); - } - private class ApiParameterContext { public ApiParameterContext( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs new file mode 100644 index 0000000000..0cd749ca8c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.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; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc +{ + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public sealed class ApiConventionAttribute : Attribute, IFilterMetadata + { + public ApiConventionAttribute(Type conventionType) + { + ConventionType = conventionType ?? throw new ArgumentNullException(nameof(conventionType)); + + if (!ConventionType.IsSealed || !ConventionType.IsAbstract) + { + // Conventions must be static viz abstract + sealed. + throw new ArgumentException(Resources.FormatApiConventionMustBeStatic(conventionType), nameof(conventionType)); + } + } + + public Type ConventionType { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs index 6114bc227f..59cc05b4e4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs @@ -84,6 +84,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels public IReadOnlyList Attributes { get; } + /// + /// Gets or sets the . + /// public ControllerModel Controller { get; set; } public IList Filters { get; } @@ -123,6 +126,9 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels string ICommonModel.Name => ActionName; + /// + /// Gets the instances. + /// public IList Selectors { get; } public string DisplayName diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs new file mode 100644 index 0000000000..a48bd43bb7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs @@ -0,0 +1,31 @@ +// 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.Http; + +namespace Microsoft.AspNetCore.Mvc +{ + public static class DefaultApiConventions + { + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + public static void Get(object id) { } + + [ProducesResponseType(StatusCodes.Status200OK)] + public static void Get() { } + + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public static void Post(TModel model) { } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public static void Put(object id, TModel model) { } + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + public static void Delete(object id) { } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 9d86de29e5..b5181b6706 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -4,6 +4,7 @@ using System; using System.Diagnostics; using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -67,6 +68,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (isApiController) { InferBoundPropertyModelPrefixes(controllerModel); + + AddGloballyConfiguredApiConventions(controllerModel); } var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null); @@ -91,6 +94,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + internal static void AddGloballyConfiguredApiConventions(ControllerModel controllerModel) + { + if (controllerModel.Filters.OfType().Any()) + { + // ApiControllerAttribute is already associated with controller. Do not look for conventions configured at assembly. + return; + } + + var assembly = controllerModel.ControllerType.Assembly; + foreach (var attribute in assembly.GetCustomAttributes()) + { + controllerModel.Filters.Add(attribute); + } + } + // Internal for unit testing internal void AddMultipartFormDataConsumesAttribute(ActionModel actionModel) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index d60a46fe66..c766cf5a59 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1453,7 +1453,7 @@ namespace Microsoft.AspNetCore.Mvc.Core => string.Format(CultureInfo.CurrentCulture, GetString("ComplexTypeModelBinder_NoParameterlessConstructor_ForParameter"), p0, p1); /// - /// Action '{0}' has more than one parameter that were specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify query string bound, '{2}' to specify route bound, and '{3}' for parameters to be bound from body: + /// Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body: /// internal static string ApiController_MultipleBodyParametersFound { @@ -1461,11 +1461,25 @@ namespace Microsoft.AspNetCore.Mvc.Core } /// - /// Action '{0}' has more than one parameter that were specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify query string bound, '{2}' to specify route bound, and '{3}' for parameters to be bound from body: + /// Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body: /// internal static string FormatApiController_MultipleBodyParametersFound(object p0, object p1, object p2, object p3) => string.Format(CultureInfo.CurrentCulture, GetString("ApiController_MultipleBodyParametersFound"), p0, p1, p2, p3); + /// + /// API convention type '{0}' must be a static type. + /// + internal static string ApiConventionMustBeStatic + { + get => GetString("ApiConventionMustBeStatic"); + } + + /// + /// API convention type '{0}' must be a static type. + /// + internal static string FormatApiConventionMustBeStatic(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMustBeStatic"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 909febc6b4..d912ecd0c5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -442,4 +442,7 @@ Action '{0}' has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use '{1}' to specify bound from query, '{2}' to specify bound from route, and '{3}' for parameters to be bound from body: + + API convention type '{0}' must be a static type. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs new file mode 100644 index 0000000000..dd72f88b2e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -0,0 +1,631 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public class ApiResponseTypeProviderTest + { + [Theory] + [InlineData("id", "model")] + [InlineData("id", "person")] + [InlineData("id", "i")] + public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotSuffix(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotExactCaseSensitiveMatch() + { + // Arrange + var parameterName = "Id"; + var conventionName = "id"; + + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("rid", "id")] + [InlineData("candid", "id")] + [InlineData("colocation", "location")] + public void IsParamterNameMatch_ReturnsFalse_IfConventionNameIsNotProperSuffix(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("id", "id")] + [InlineData("model", "model")] + public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatch(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("id", "_id")] + [InlineData("model", "_model")] + public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatchIgnoringLeadingUnderscores(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("personId", "id")] + [InlineData("userModel", "model")] + [InlineData("beaconLocation", "Location")] + public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffix(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("personId", "_id")] + [InlineData("userModel", "_model")] + [InlineData("userModel", "__model")] + public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffixIgnoringLeadingUnderscores(string parameterName, string conventionName) + { + // Act + var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsParameterTypeMatch_ReturnsFalse_ForUnrelatedTypes() + { + // Arrange + var type = typeof(string); + var conventionType = typeof(int); + + // Act + var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsParameterTypeMatch_ReturnsFalse_IfTypeIsBaseClassOfConvention() + { + // Arrange + var type = typeof(BaseModel); + var conventionType = typeof(DerivedModel); + + // Act + var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsExact() + { + // Arrange + var type = typeof(Uri); + var conventionType = typeof(Uri); + + // Act + var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsSubtypeOfConvention() + { + // Arrange + var type = typeof(DerivedModel); + var conventionType = typeof(BaseModel); + + // Act + var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData(typeof(int))] + [InlineData(typeof(DerivedModel))] + public void IsParameterTypeMatch_ReturnsTrue_IfConventionTypeIsObject(Type type) + { + // Arrange + var conventionType = typeof(object); + + // Act + var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("Get", "Post")] + [InlineData("Post", "Get")] + [InlineData("PostPerson", "Put")] + public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotPrefix(string methodName, string conventionMethodName) + { + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("PostalService", "Post")] + [InlineData("Listings", "List")] + [InlineData("Putt", "Put")] + public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotProperPrefix(string methodName, string conventionMethodName) + { + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsExactMatch() + { + // Arrange + var methodName = "Post"; + var conventionMethodName = "Post"; + + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsExactMatchWithDifferentCasing() + { + // Arrange + var methodName = "post"; + var conventionMethodName = "Post"; + + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("PostPerson", "Post")] + [InlineData("GetById", "Get")] + [InlineData("SearchList", "Search")] + public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsProperSuffix(string methodName, string conventionMethodName) + { + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsProperSuffix_WithDifferentCasing() + { + // Arrange + var methodName = "getById"; + var conventionMethodName = "Get"; + + // Act + var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfMethodNamesAreNotMatches() + { + // Arrange + var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Post)); + var method = typeof(TestController).GetMethod(nameof(TestController.GetUser)); + + // Act + var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfParameterCountsDoNotMatch() + { + // Arrange + var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) }); + var method = typeof(TestController).GetMethod(nameof(TestController.GetUserLocation)); + + // Act + var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_ForMethodWithObjectParameter() + { + // Arrange + var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) }); + var method = typeof(TestController).GetMethod(nameof(TestController.GetUser)); + + // Act + var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_ForConventionWithGenericParameter() + { + // Arrange + var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Put)); + var method = typeof(TestController).GetMethod(nameof(TestController.PutModel)); + + // Act + var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresent() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)); + + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(301, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [ApiConvention(typeof(DefaultApiConventions))] + public class GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController : ControllerBase + { + [Produces(typeof(BaseModel))] + [ProducesResponseType(301)] + [ProducesResponseType(404)] + public Task> Get(int id) => null; + } + + [Fact] + public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventions() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [ApiConvention(typeof(DefaultApiConventions))] + public class GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController : ControllerBase + { + public Task> DeleteBase(int id) => null; + } + + [Fact] + public void GetApiResponseTypes_ReturnsResponseTypesFromCustomConventions() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController.SearchModel)); + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(206, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(406, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [ApiConvention(typeof(SearchApiConventions))] + public class GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController : ControllerBase + { + public Task> SearchModel(string searchTerm, int page) => null; + } + + [Fact] + public void GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConvention_WhenMultipleConventionsArePresent() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.SearchModel)); + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(206, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(406, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [ApiConvention(typeof(DefaultApiConventions))] + [ApiConvention(typeof(SearchApiConventions))] + public class GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController : ControllerBase + { + public Task> Get(int id) => null; + + public Task> SearchModel(string searchTerm, int page) => null; + } + + [Fact] + public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConvention_WhenMultipleConventionsArePresent() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.Get)); + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [Fact] + public void GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatch() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController), + nameof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController.PostModel)); + var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); + actionDescriptor.FilterDescriptors.Add(filter); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [ApiConvention(typeof(DefaultApiConventions))] + public class GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController : ControllerBase + { + public Task> PostModel(int id, BaseModel model) => null; + } + + private static ApiResponseTypeProvider GetProvider() + { + var mvcOptions = new MvcOptions + { + OutputFormatters = { new TestOutputFormatter() }, + }; + var provider = new ApiResponseTypeProvider(new EmptyModelMetadataProvider(), new ActionResultTypeMapper(), mvcOptions); + return provider; + } + + private static ControllerActionDescriptor GetControllerActionDescriptor(Type type, string name) + { + var method = type.GetMethod(name); + var actionDescriptor = new ControllerActionDescriptor + { + MethodInfo = method, + FilterDescriptors = new List(), + }; + + foreach (var filterAttribute in method.GetCustomAttributes().OfType()) + { + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(filterAttribute, FilterScope.Action)); + } + + return actionDescriptor; + } + + public class BaseModel { } + + public class DerivedModel : BaseModel { } + + public class TestController + { + public ActionResult GetUser(int id) => null; + + public ActionResult GetUserLocation(int a, int b) => null; + + public ActionResult PutModel(string userId, DerivedModel model) => null; + } + + private class TestOutputFormatter : OutputFormatter + { + public TestOutputFormatter() + { + SupportedMediaTypes.Add(new Net.Http.Headers.MediaTypeHeaderValue("application/json")); + } + + public override Task WriteResponseBodyAsync(OutputFormatterWriteContext context) => Task.CompletedTask; + } + + public static class SearchApiConventions + { + [ProducesResponseType(206)] + [ProducesResponseType(406)] + public static void Search(object searchTerm, int page) { } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index bb28383a58..c0c83f07ef 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -5,6 +5,7 @@ using System; using System.ComponentModel; using System.Linq; using System.Reflection; +using System.Reflection.Emit; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -873,6 +874,59 @@ Environment.NewLine + "int b"; Assert.Equal("multipart/form-data", Assert.Single(consumesAttribute.ContentTypes)); } + [Fact] + public void ApiConventionAttributeIsNotAdded_IfModelAlreadyHasAttribute() + { + // Arrange + var attribute = new ApiConventionAttribute(typeof(DefaultApiConventions)); + var controllerType = CreateTestControllerType(); + + var model = new ControllerModel(controllerType.GetTypeInfo(), new[] { attribute }) + { + Filters = { attribute, }, + }; + + // Act + ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model); + + // Assert + Assert.Collection( + model.Filters, + filter => Assert.Same(attribute, filter)); + } + + [Fact] + public void ApiConventionAttributeIsAdded_IfAttributeExistsInAssembly() + { + // Arrange + var controllerType = CreateTestControllerType(); + var model = new ControllerModel(controllerType.GetTypeInfo(), Array.Empty()); + + // Act + ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model); + + // Assert + Assert.Collection( + model.Filters, + filter => Assert.IsType(filter)); + } + + // A dynamically generated type in an assembly that has an ApiConventionAttribute. + private static TypeBuilder CreateTestControllerType() + { + var attributeBuilder = new CustomAttributeBuilder( + typeof(ApiConventionAttribute).GetConstructor(new[] { typeof(Type) }), + new[] { typeof(DefaultApiConventions) }); + + var assemblyName = new AssemblyName("TestAssembly"); + var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); + assemblyBuilder.SetCustomAttribute(attributeBuilder); + + var module = assemblyBuilder.DefineDynamicModule(assemblyName.Name); + var controllerType = module.DefineType("TestController"); + return controllerType; + } + private static ApiBehaviorApplicationModelProvider GetProvider( ApiBehaviorOptions options = null, IModelMetadataProvider modelMetadataProvider = null) From 287a3c5e6927a4fc3cf10e279b1e961359961596 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 12 Jun 2018 12:13:59 -0700 Subject: [PATCH 051/316] Allow IgnoreAntiForgeryToken applied on Razor Page models to work Fixes #7795 --- .../Authorization/AuthorizeFilter.cs | 15 +++- ...AntiforgeryPageApplicationModelProvider.cs | 41 +++++++++ ...AntiforgeryPageApplicationModelProvider.cs | 31 ------- .../AntiforgeryTestHelper.cs | 39 ++++---- .../RazorPagesWithBasePathTest.cs | 67 ++++++++++++++ ...forgeryPageApplicationModelProviderTest.cs | 89 +++++++++++++++++++ ...AntiforgeryPageApplicationModelProvider.cs | 36 -------- .../Antiforgery/AntiforgeryDefault.cshtml | 5 ++ .../Antiforgery/AntiforgeryDefault.cshtml.cs | 11 +++ .../Antiforgery/IgnoreAntiforgery.cshtml | 5 ++ .../Antiforgery/IgnoreAntiforgery.cshtml.cs | 13 +++ .../Pages/Antiforgery/_ViewImports.cshtml | 1 + 12 files changed, 269 insertions(+), 84 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs index abf76ea191..727ac3d659 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.Authorization var authenticateResult = await policyEvaluator.AuthenticateAsync(effectivePolicy, context.HttpContext); // Allow Anonymous skips all authorization - if (context.Filters.Any(item => item is IAllowAnonymousFilter)) + if (HasAllowAnonymous(context.Filters)) { return; } @@ -218,5 +218,18 @@ namespace Microsoft.AspNetCore.Mvc.Authorization var policyProvider = serviceProvider.GetRequiredService(); return AuthorizationApplicationModelProvider.GetFilter(policyProvider, AuthorizeData); } + + private static bool HasAllowAnonymous(IList filters) + { + for (var i = 0; i < filters.Count; i++) + { + if (filters[i] is IAllowAnonymousFilter) + { + return true; + } + } + + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs new file mode 100644 index 0000000000..5503ba5d03 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ViewFeatures; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + internal class AutoValidateAntiforgeryPageApplicationModelProvider : IPageApplicationModelProvider + { + // The order is set to execute after the DefaultPageApplicationModelProvider. + public int Order => -1000 + 10; + + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + } + + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + var pageApplicationModel = context.PageApplicationModel; + + // ValidateAntiforgeryTokenAttribute relies on order to determine if it's the effective policy. + // When two antiforgery filters of the same order are added to the application model, the effective policy is determined + // by whatever appears later in the list (closest to the action). This causes filters listed on the model to be pre-empted + // by the one added here. We'll resolve this unusual behavior by skipping the addition of the AutoValidateAntiforgeryTokenAttribute + // when another already exists. + if (!pageApplicationModel.Filters.OfType().Any()) + { + // Always require an antiforgery token on post + pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs deleted file mode 100644 index 2103a4606b..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Mvc.ApplicationModels; - -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal -{ - public class AutoValidateAntiforgeryPageApplicationModelProvider : IPageApplicationModelProvider - { - // The order is set to execute after the DefaultPageApplicationModelProvider. - public int Order => -1000 + 10; - - public void OnProvidersExecuted(PageApplicationModelProviderContext context) - { - } - - public void OnProvidersExecuting(PageApplicationModelProviderContext context) - { - if (context == null) - { - throw new ArgumentNullException(nameof(context)); - } - - var pageApplicationModel = context.PageApplicationModel; - - // Always require an antiforgery token on post - pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs index d7310e628b..17ff0bb735 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs @@ -2,37 +2,44 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.IO; using System.Linq; using System.Net.Http; -using System.Xml.Linq; +using AngleSharp.Dom.Html; +using AngleSharp.Parser.Html; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public static class AntiforgeryTestHelper { + public static string RetrieveAntiforgeryToken(string htmlContent) + => RetrieveAntiforgeryToken(htmlContent, actionUrl: string.Empty); + public static string RetrieveAntiforgeryToken(string htmlContent, string actionUrl) { - htmlContent = "" + htmlContent + ""; - var reader = new StringReader(htmlContent); - var htmlDocument = XDocument.Load(reader); + var parser = new HtmlParser(); + var htmlDocument = parser.Parse(htmlContent); - foreach (var form in htmlDocument.Descendants("form")) + return RetrieveAntiforgeryToken(htmlDocument); + } + + public static string RetrieveAntiforgeryToken(IHtmlDocument htmlDocument) + { + var hiddenInputs = htmlDocument.QuerySelectorAll("form input[type=hidden]"); + foreach (var input in hiddenInputs) { - foreach (var input in form.Descendants("input")) + if (!input.HasAttribute("name")) { - if (input.Attribute("name") != null && - input.Attribute("type") != null && - input.Attribute("type").Value == "hidden" && - (input.Attribute("name").Value == "__RequestVerificationToken" || - input.Attribute("name").Value == "HtmlEncode[[__RequestVerificationToken]]")) - { - return input.Attributes("value").First().Value; - } + continue; + } + + var name = input.GetAttribute("name"); + if (name == "__RequestVerificationToken" || name == "HtmlEncode[[__RequestVerificationToken]]") + { + return input.GetAttribute("value"); } } - throw new Exception($"Antiforgery token could not be located in {htmlContent}."); + throw new Exception($"Antiforgery token could not be located in {htmlDocument.TextContent}."); } public static CookieMetadata RetrieveAntiforgeryCookie(HttpResponseMessage response) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 680af29004..6d43c53c97 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -552,5 +552,72 @@ Hello from /Pages/Shared/"; var title = document.QuerySelector("title").TextContent; Assert.Equal("View Data in Pages", title); } + + [Fact] + public async Task Antiforgery_RequestWithoutAntiforgeryToken_Returns200ForHeadRequests() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Head, "/Antiforgery/AntiforgeryDefault"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Antiforgery_RequestWithoutAntiforgeryToken_Returns400BadRequest() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "/Antiforgery/AntiforgeryDefault"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task Antiforgery_RequestWithAntiforgeryToken_Succeeds() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "/Antiforgery/AntiforgeryDefault"); + await AddAntiforgeryHeadersAsync(request); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Antiforgery_IgnoreAntiforgeryTokenAppliedToModelWorks() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "/Antiforgery/IgnoreAntiforgery"); + await AddAntiforgeryHeadersAsync(request); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + private async Task AddAntiforgeryHeadersAsync(HttpRequestMessage request) + { + var response = await Client.GetAsync(request.RequestUri); + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var responseBody = await response.Content.ReadAsStringAsync(); + var formToken = AntiforgeryTestHelper.RetrieveAntiforgeryToken(responseBody); + var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response); + + request.Headers.Add("Cookie", cookie.Key + "=" + cookie.Value); + request.Headers.Add("RequestVerificationToken", formToken); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs new file mode 100644 index 0000000000..f3222ce36c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ViewFeatures; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.RazorPages +{ + public class AutoValidateAntiforgeryPageApplicationModelProviderTest + { + [Fact] + public void OnProvidersExecuting_AddsFiltersToModel() + { + // Arrange + var actionDescriptor = new PageActionDescriptor(); + var applicationModel = new PageApplicationModel( + actionDescriptor, + typeof(object).GetTypeInfo(), + new object[0]); + var applicationModelProvider = new AutoValidateAntiforgeryPageApplicationModelProvider(); + var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo()) + { + PageApplicationModel = applicationModel, + }; + + // Act + applicationModelProvider.OnProvidersExecuting(context); + + // Assert + Assert.Collection( + applicationModel.Filters, + filter => Assert.IsType(filter)); + } + + [Fact] + public void OnProvidersExecuting_DoesNotAddAutoValidateAntiforgeryTokenAttribute_IfIgnoreAntiforgeryTokenAttributeExists() + { + // Arrange + var expected = new IgnoreAntiforgeryTokenAttribute(); + + var descriptor = new PageActionDescriptor(); + var provider = new AutoValidateAntiforgeryPageApplicationModelProvider(); + var context = new PageApplicationModelProviderContext(descriptor, typeof(object).GetTypeInfo()) + { + PageApplicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty()) + { + Filters = { expected }, + }, + }; + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection( + context.PageApplicationModel.Filters, + actual => Assert.Same(expected, actual)); + } + + [Fact] + public void OnProvidersExecuting_DoesNotAddAutoValidateAntiforgeryTokenAttribute_IfAntiforgeryPolicyExists() + { + // Arrange + var expected = Mock.Of(); + + var descriptor = new PageActionDescriptor(); + var provider = new AutoValidateAntiforgeryPageApplicationModelProvider(); + var context = new PageApplicationModelProviderContext(descriptor, typeof(object).GetTypeInfo()) + { + PageApplicationModel = new PageApplicationModel(descriptor, typeof(object).GetTypeInfo(), Array.Empty()) + { + Filters = { expected }, + }, + }; + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection( + context.PageApplicationModel.Filters, + actual => Assert.Same(expected, actual)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs deleted file mode 100644 index 20d81e5c69..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AutoValidateAntiforgeryPageApplicationModelProvider.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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.Reflection; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal -{ - public class AutoValidateAntiforgeryPageApplicationModelProviderTest - { - [Fact] - public void OnProvidersExecuting_AddsFiltersToModel() - { - // Arrange - var actionDescriptor = new PageActionDescriptor(); - var applicationModel = new PageApplicationModel( - actionDescriptor, - typeof(object).GetTypeInfo(), - new object[0]); - var applicationModelProvider = new AutoValidateAntiforgeryPageApplicationModelProvider(); - var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeof(object).GetTypeInfo()) - { - PageApplicationModel = applicationModel, - }; - - // Act - applicationModelProvider.OnProvidersExecuting(context); - - // Assert - Assert.Collection( - applicationModel.Filters, - filter => Assert.IsType(filter)); - } - } -} diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml new file mode 100644 index 0000000000..9e36e83e2a --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml @@ -0,0 +1,5 @@ +@page +@model AntiforgeryDefaultModel +
+ +
diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs new file mode 100644 index 0000000000..9f56cbd7e9 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite +{ + public class AntiforgeryDefaultModel : PageModel + { + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml new file mode 100644 index 0000000000..336e79a803 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml @@ -0,0 +1,5 @@ +@page +@model IgnoreAntiforgeryModel +
+ +
diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs new file mode 100644 index 0000000000..34c4c739b7 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite +{ + [IgnoreAntiforgeryToken] + public class IgnoreAntiforgeryModel : PageModel + { + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml new file mode 100644 index 0000000000..aaf882de29 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml @@ -0,0 +1 @@ +@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" From a7406d44977a84edb132976d0cfa6552c0faaaa3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 14 Jun 2018 11:03:45 +1200 Subject: [PATCH 052/316] Add MvcEndpointDataSource and functional tests (#7886) --- Mvc.sln | 15 + build/dependencies.props | 148 +- .../MvcCoreServiceCollectionExtensions.cs | 7 + .../Internal/MvcEndpointDataSource.cs | 139 ++ .../Internal/MvcEndpointInvokerFactory.cs | 41 + .../MvcCoreServiceCollectionExtensionsTest.cs | 7 + .../Internal/MvcEndpointDataSourceTests.cs | 170 +++ .../DispatchingTests.cs | 1259 +++++++++++++++++ ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 1 + .../Areas/Admin/UserManagementController.cs | 25 + .../Areas/Order/OrderController.cs | 25 + .../Areas/Travel/FlightController.cs | 30 + .../Areas/Travel/HomeController.cs | 29 + .../Areas/Travel/RailController.cs | 30 + .../Controllers/BanksController.cs | 52 + .../Controllers/BlogController.cs | 29 + .../Controllers/CompanyController.cs | 63 + .../Controllers/EmployeeController.cs | 73 + .../Controllers/FriendsController.cs | 31 + .../Controllers/HomeController.cs | 39 + .../Controllers/MapsController.cs | 53 + .../Controllers/OrderController.cs | 36 + .../Controllers/StoreController.cs | 43 + .../Controllers/TeamController.cs | 72 + .../DispatchingWebSite.csproj | 15 + .../DispatchingWebSite/HttpMergeAttribute.cs | 34 + test/WebSites/DispatchingWebSite/Startup.cs | 78 + .../TestResponseGenerator.cs | 61 + test/WebSites/DispatchingWebSite/readme.md | 4 + 29 files changed, 2535 insertions(+), 74 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs create mode 100644 test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs create mode 100644 test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs create mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs create mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs create mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/BanksController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/BlogController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/HomeController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/MapsController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/OrderController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/StoreController.cs create mode 100644 test/WebSites/DispatchingWebSite/Controllers/TeamController.cs create mode 100644 test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj create mode 100644 test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs create mode 100644 test/WebSites/DispatchingWebSite/Startup.cs create mode 100644 test/WebSites/DispatchingWebSite/TestResponseGenerator.cs create mode 100644 test/WebSites/DispatchingWebSite/readme.md diff --git a/Mvc.sln b/Mvc.sln index 1eb7f54602..830bd9f81e 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -172,6 +172,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatchingWebSite", "test\WebSites\DispatchingWebSite\DispatchingWebSite.csproj", "{ABB3737F-E518-4E40-8A9C-F3281D610E8F}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -906,6 +908,18 @@ Global {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.ActiveCfg = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.Build.0 = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.Build.0 = Debug|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.Build.0 = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.ActiveCfg = Release|Any CPU + {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -976,6 +990,7 @@ Global {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {ABB3737F-E518-4E40-8A9C-F3281D610E8F} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/build/dependencies.props b/build/dependencies.props index d3cfd72116..3f7994e63f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34425 + 2.2.0-preview1-34462 2.2.0-preview1-17082 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34425 + 2.2.0-preview1-34462 1.7.0 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-26606-01 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-26612-04 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 2.0.0 2.1.0 - 2.2.0-preview1-26606-01 - 2.2.0-preview1-34425 - 2.2.0-preview1-34425 + 2.2.0-preview1-26612-04 + 2.2.0-preview1-34462 + 2.2.0-preview1-34462 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26605-01 - 4.6.0-preview1-26605-01 - 4.6.0-preview1-26605-01 + 4.6.0-preview1-26611-02 + 4.6.0-preview1-26611-02 + 4.6.0-preview1-26611-02 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index c0bfe2039e..84b77646bc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -260,6 +260,13 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); // Only one per app services.TryAddTransient(); // Many per app + // + // Dispatching + // + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); + services.TryAddSingleton(); + // // Middleware pipeline filter related // diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs new file mode 100644 index 0000000000..131e545e67 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -0,0 +1,139 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.EndpointConstraints; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal class MvcEndpointDataSource : EndpointDataSource + { + private readonly IActionDescriptorCollectionProvider _actions; + private readonly MvcEndpointInvokerFactory _invokerFactory; + private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; + private readonly List _endpoints; + + private IChangeToken _changeToken; + + public MvcEndpointDataSource( + IActionDescriptorCollectionProvider actions, + MvcEndpointInvokerFactory invokerFactory, + IEnumerable actionDescriptorChangeProviders) + { + if (actions == null) + { + throw new ArgumentNullException(nameof(actions)); + } + + if (invokerFactory == null) + { + throw new ArgumentNullException(nameof(invokerFactory)); + } + + if (actionDescriptorChangeProviders == null) + { + throw new ArgumentNullException(nameof(actionDescriptorChangeProviders)); + } + + _actions = actions; + _invokerFactory = invokerFactory; + _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); + + _endpoints = new List(); + + InitializeEndpoints(); + } + + private void InitializeEndpoints() + { + // note: this code has haxxx. This will only work in some constrained scenarios + foreach (var action in _actions.ActionDescriptors.Items) + { + if (action.AttributeRouteInfo == null) + { + // Action does not have an attribute route + continue; + } + + RequestDelegate invokerDelegate = (context) => + { + var values = context.Features.Get().Values; + var routeData = new RouteData(); + foreach (var kvp in values) + { + routeData.Values.Add(kvp.Key, kvp.Value); + } + + var actionContext = new ActionContext(context, routeData, action); + + var invoker = _invokerFactory.CreateInvoker(actionContext); + return invoker.InvokeAsync(); + }; + + var metadata = new List(); + + // Add filter descriptors to endpoint metadata + metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter)); + + if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) + { + foreach (var actionConstraint in action.ActionConstraints) + { + if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint) + { + metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods)); + } + } + } + + var metadataCollection = new EndpointMetadataCollection(metadata); + + _endpoints.Add(new MatcherEndpoint( + next => invokerDelegate, + action.AttributeRouteInfo.Template, + action.RouteValues, + action.AttributeRouteInfo.Order, + metadataCollection, + action.DisplayName)); + } + } + + private IChangeToken GetCompositeChangeToken() + { + if (_actionDescriptorChangeProviders.Length == 1) + { + return _actionDescriptorChangeProviders[0].GetChangeToken(); + } + + var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length]; + for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++) + { + changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken(); + } + + return new CompositeChangeToken(changeTokens); + } + + public override IChangeToken ChangeToken + { + get + { + if (_changeToken == null) + { + _changeToken = GetCompositeChangeToken(); + } + + return _changeToken; + } + } + + public override IReadOnlyList Endpoints => _endpoints; + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs new file mode 100644 index 0000000000..3737a3b718 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs @@ -0,0 +1,41 @@ +// 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.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal sealed class MvcEndpointInvokerFactory : IActionInvokerFactory + { + private readonly IActionInvokerFactory _invokerFactory; + private readonly IActionContextAccessor _actionContextAccessor; + + public MvcEndpointInvokerFactory( + IActionInvokerFactory invokerFactory) + : this(invokerFactory, actionContextAccessor: null) + { + } + + public MvcEndpointInvokerFactory( + IActionInvokerFactory invokerFactory, + IActionContextAccessor actionContextAccessor) + { + _invokerFactory = invokerFactory; + + // The IActionContextAccessor is optional. We want to avoid the overhead of using CallContext + // if possible. + _actionContextAccessor = actionContextAccessor; + } + + public IActionInvoker CreateInvoker(ActionContext actionContext) + { + if (_actionContextAccessor != null) + { + _actionContextAccessor.ActionContext = actionContext; + } + + return _invokerFactory.CreateInvoker(actionContext); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index 237eb47046..e2e7412818 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -306,6 +306,13 @@ namespace Microsoft.AspNetCore.Mvc typeof(ApiBehaviorApplicationModelProvider), } }, + { + typeof(EndpointDataSource), + new Type[] + { + typeof(MvcEndpointDataSource), + } + }, }; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs new file mode 100644 index 0000000000..75eac34e56 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -0,0 +1,170 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.Primitives; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal +{ + public class MvcEndpointDataSourceTests + { + [Fact] + public void Endpoints_AccessParameters_InitializedFromProvider() + { + // Arrange + var routeValue = "Value"; + var routeValues = new Dictionary + { + ["Name"] = routeValue + }; + var displayName = "DisplayName!"; + var order = 1; + var template = "/Template!"; + var filterDescriptor = new FilterDescriptor(new ControllerActionFilter(), 1); + + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + new ActionDescriptor + { + RouteValues = routeValues, + DisplayName = displayName, + AttributeRouteInfo = new AttributeRouteInfo + { + Order = order, + Template = template + }, + FilterDescriptors = new List + { + filterDescriptor + } + } + }, 0)); + + // Act + var dataSource = new MvcEndpointDataSource( + mockDescriptorProvider.Object, + new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), + Array.Empty()); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + + object endpointValue = matcherEndpoint.Values["Name"]; + Assert.Equal(routeValue, endpointValue); + + Assert.Equal(displayName, matcherEndpoint.DisplayName); + Assert.Equal(order, matcherEndpoint.Order); + Assert.Equal(template, matcherEndpoint.Template); + } + + [Fact] + public void Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled() + { + // Arrange + var featureCollection = new FeatureCollection(); + featureCollection.Set(new EndpointFeature + { + Values = new RouteValueDictionary() + }); + + var httpContextMock = new Mock(); + httpContextMock.Setup(m => m.Features).Returns(featureCollection); + + var mockDescriptorProviderMock = new Mock(); + mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + new ActionDescriptor + { + AttributeRouteInfo = new AttributeRouteInfo + { + Template = string.Empty + }, + FilterDescriptors = new List() + } + }, 0)); + + var actionInvokerCalled = false; + var actionInvokerMock = new Mock(); + actionInvokerMock.Setup(m => m.InvokeAsync()).Returns(() => + { + actionInvokerCalled = true; + return Task.CompletedTask; + }); + + var actionInvokerProviderMock = new Mock(); + actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny())).Returns(actionInvokerMock.Object); + + // Act + var dataSource = new MvcEndpointDataSource( + mockDescriptorProviderMock.Object, + new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object), + Array.Empty()); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + + var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask); + + invokerDelegate(httpContextMock.Object); + + Assert.True(actionInvokerCalled); + } + + [Fact] + public void ChangeToken_MultipleChangeTokenProviders_ComposedResult() + { + // Arrange + var featureCollection = new FeatureCollection(); + featureCollection.Set(new EndpointFeature + { + Values = new RouteValueDictionary() + }); + + var httpContextMock = new Mock(); + httpContextMock.Setup(m => m.Features).Returns(featureCollection); + + var mockDescriptorProviderMock = new Mock(); + mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); + + var actionInvokerMock = new Mock(); + + var actionInvokerProviderMock = new Mock(); + actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny())).Returns(actionInvokerMock.Object); + + var changeTokenMock = new Mock(); + + var changeProvider1Mock = new Mock(); + changeProvider1Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object); + var changeProvider2Mock = new Mock(); + changeProvider2Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object); + + var dataSource = new MvcEndpointDataSource( + mockDescriptorProviderMock.Object, + new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object), + new[] { changeProvider1Mock.Object, changeProvider2Mock.Object }); + + // Act + var changeToken = dataSource.ChangeToken; + + // Assert + var compositeChangeToken = Assert.IsType(changeToken); + Assert.Equal(2, compositeChangeToken.ChangeTokens.Count); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs new file mode 100644 index 0000000000..99d7472b60 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -0,0 +1,1259 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class DispatchingTests : IClassFixture> + { + public DispatchingTests(MvcTestFixture fixture) + { + Client = fixture.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedController_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/Index", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedController_NonActionIsNotReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedController_InArea_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "area", "Travel" }, + { "controller", "Flight" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory(Skip = "Conventional routing WIP")] + [InlineData("", "/Home/OptionalPath/default")] + [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] + public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact] + public async Task AttributeRoutedAction_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("ListProducts", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Store"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "ListProducts"), + result.RouteValues); + } + + [Theory] + [InlineData("Get", "/Friends")] + [InlineData("Get", "/Friends/Peter")] + [InlineData("Delete", "/Friends")] + public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(url, result.ExpectedUrls); + Assert.Equal("Friends", result.Controller); + Assert.Equal(method, result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Friends"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", method), + result.RouteValues); + + if (result.RouteValues.ContainsKey("id")) + { + Assert.Contains( + new KeyValuePair("id", "Peter"), + result.RouteValues); + } + } + + [Theory] + [InlineData("Post", "/Friends")] + [InlineData("Put", "/Friends")] + [InlineData("Patch", "/Friends")] + [InlineData("Options", "/Friends")] + [InlineData("Head", "/Friends")] + public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory(Skip = "URL generation WIP")] + [InlineData("http://localhost/api/v1/Maps")] + [InlineData("http://localhost/api/v2/Maps")] + public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) + { + // Arrange & Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v1/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() + { + // Arrange + var url = "http://localhost/api/v2/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Post", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact] + public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + { + // Arrange + var url = "http://localhost/api/v1/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory(Skip = "URL generation WIP")] + [InlineData("http://localhost/api/v1/Maps/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/5", "PUT")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] + public async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( + string url, + string method) + { + // Arrange & Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Update", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps/PartialUpdate/5", + "/api/v2/Maps/PartialUpdate/5" + }, + result.ExpectedUrls); + } + + [Theory(Skip = "URL generation WIP")] + [InlineData("http://localhost/Banks/Get/5")] + [InlineData("http://localhost/Bank/Get/5")] + public async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Banks", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/Bank/Get/5", + "/Bank/Get/5" + }, + result.ExpectedUrls); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + string url, + string method) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + // The url would be /Store/ListProducts with conventional routes + [Fact] + public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/ListProducts"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + // There's two actions at this URL - but attribute routes go in the route table + // first. + [Fact] + public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/About"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/About", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("About", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); + Assert.Equal("Blog", result.Controller); + Assert.Equal("Edit", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Blog"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "Edit"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("postId", "5"), + result.RouteValues); + } + + // There's no [HttpGet] on the action here. + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Employee"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + } + + // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller + // and a test that verifies that if you define another action with a specific verb we'll route to that + // more specific action. + [Theory] + [InlineData("PUT")] + [InlineData("POST")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); + + // 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("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("Orders", result.Action); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); + + // 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/Employee/5/Salary", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("Salary", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); + + // 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("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("GetOrders", result.Action); + } + + // There's no [HttpGet] on the action here. + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); + + // 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/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateEmployee", result.Action); + } + + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); + + // 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/Employee/Manager", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateManager", result.Action); + } + + [Theory(Skip = "URL generation WIP")] + [InlineData("PUT", "Bank")] + [InlineData("PATCH", "Bank")] + [InlineData("PUT", "Bank/Update")] + [InlineData("PATCH", "Bank/Update")] + public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) + { + // Arrange + var expectedUrl = "/Bank/Update"; + var message = new HttpRequestMessage(new HttpMethod(verb), "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(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal("UpdateBank", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/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.Contains("/api/Employee/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("MergeEmployee", result.Action); + } + + // There's an [HttpGet] with its own template on the action here. + [Theory] + [InlineData("GET", "GetAdministrator")] + [InlineData("DELETE", "DeleteAdministrator")] + public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); + + // 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/Employee/5/Administrator", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal(action, result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Manager/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Manager/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("GetManager", result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Team/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Team/5", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganization", result.Action); + + Assert.Contains( + new KeyValuePair("teamId", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Teams"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Teams", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganizations", result.Action); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Organization/5"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Club/5", response); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Teams/AllOrganizations", response); + } + + [Theory] + [InlineData("", "/TeamName/DefaultName")] + [InlineData("CustomName", "/TeamName/CustomName")] + public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) + { + // Arrange & Act + var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); + + // Assert + Assert.NotNull(body); + var result = JsonConvert.DeserializeObject(body); + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkToSelf() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkWithAmbientController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee/5", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkToAttributeRoutedController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/Blog/ShowPosts", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkToConventionalController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/", result.Link); + } + + [Theory(Skip = "URL generation WIP")] + [InlineData("GET", "Get")] + [InlineData("PUT", "Put")] + public async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( + string method, + string actionName) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/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("Company", result.Controller); + Assert.Equal(actionName, result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("Company", result.RouteName); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + { + // Arrange & Act + var response = await Client.DeleteAsync("http://localhost/api/Company/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("Delete", result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("RemoveCompany", result.RouteName); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() + { + // Arrange + var url = LinkFrom("http://localhost") + .To(new { id = 5 }); + + // Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetEmployees", result.Action); + + Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); + Assert.Null(result.RouteName); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetDepartments", result.Action); + + Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); + Assert.Equal("Departments", result.RouteName); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/") + .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_InArea_StaysInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee") + .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Index", controller = "Flight", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight", result.Link); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Rail", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact(Skip = "Conventional routing WIP")] + public async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Fact(Skip = "URL generation WIP")] + public async Task AttributeRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Theory] + [InlineData("/Bank/Deposit", "PUT", "Deposit")] + [InlineData("/Bank/Deposit", "POST", "Deposit")] + [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] + [InlineData("/Bank/Deposit/5", "POST", "Deposit")] + [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] + public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal(actionName, result.Action); + } + + // These verbs don't match + [Theory] + [InlineData("/Bank/Deposit", "GET")] + [InlineData("/Bank/Deposit/5", "DELETE")] + [InlineData("/Bank/Withdraw/5", "GET")] + public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("/Order/Add/1", "GET", "Add")] + [InlineData("/Order/Add", "POST", "Add")] + [InlineData("/Order/Edit/1", "PUT", "Edit")] + [InlineData("/Order/GetOrder", "GET", "GetOrder")] + public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Order", result.Controller); + Assert.Equal(actionName, result.Action); + } + + private static LinkBuilder LinkFrom(string url) + { + return new LinkBuilder(url); + } + + // See TestResponseGenerator in RoutingWebSite for the code that generates this data. + 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; } + } + + private class LinkBuilder + { + public LinkBuilder(string url) + { + Url = url; + + Values = new Dictionary(); + Values.Add("link", string.Empty); + } + + public string Url { get; set; } + + public Dictionary Values { get; set; } + + public LinkBuilder To(object values) + { + var dictionary = new RouteValueDictionary(values); + foreach (var kvp in dictionary) + { + Values.Add("link_" + kvp.Key, kvp.Value); + } + + return this; + } + + public override string ToString() + { + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); + } + + public static implicit operator string(LinkBuilder builder) + { + return builder.ToString(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index d9bafa174e..a3d5754be1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -31,6 +31,7 @@ + diff --git a/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs b/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs new file mode 100644 index 0000000000..9d58e35998 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs @@ -0,0 +1,25 @@ +// 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; + +namespace DispatchingWebSite.Admin +{ + [Area("Admin")] + [Route("[area]/Users")] + public class UserManagementController : Controller + { + private readonly TestResponseGenerator _generator; + + public UserManagementController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet("All")] + public IActionResult ListUsers() + { + return _generator.Generate("Admin/Users/All"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs b/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs new file mode 100644 index 0000000000..76e83adf58 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs @@ -0,0 +1,25 @@ +// 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; + +namespace DispatchingWebSite.Areas.Order +{ + [Area("Order")] + [Route("Order/[action]", Name = "[area]_[action]")] + public class OrderController : Controller + { + private readonly TestResponseGenerator _generator; + + public OrderController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet] + public IActionResult GetOrder() + { + return _generator.Generate("/Order/GetOrder"); + } + } +} diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs new file mode 100644 index 0000000000..adaf6e46cc --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs @@ -0,0 +1,30 @@ +// 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; + +namespace DispatchingWebSite +{ + // This controller is reachable via traditional routing. + [Area("Travel")] + public class FlightController + { + private readonly TestResponseGenerator _generator; + + public FlightController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Index() + { + return _generator.Generate("/Travel/Flight", "/Travel/Flight/Index"); + } + + [HttpPost] + public IActionResult BuyTickets() + { + return _generator.Generate("/Travel/Flight/BuyTickets"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs new file mode 100644 index 0000000000..51b20ea566 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs @@ -0,0 +1,29 @@ +// 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; + +namespace DispatchingWebSite.Travel +{ + [Area("Travel")] + public class HomeController : Controller + { + private readonly TestResponseGenerator _generator; + + public HomeController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Index() + { + return _generator.Generate("/Travel", "/Travel/Home", "/Travel/Home/Index"); + } + + [HttpGet("ContosoCorp/AboutTravel")] + public IActionResult About() + { + return _generator.Generate(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs new file mode 100644 index 0000000000..c12bcd8c6f --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs @@ -0,0 +1,30 @@ +// 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; + +namespace DispatchingWebSite +{ + [Area("Travel")] + [Route("ContosoCorp/Trains")] + public class RailController + { + private readonly TestResponseGenerator _generator; + + public RailController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Index() + { + return _generator.Generate("/ContosoCorp/Trains"); + } + + [HttpGet("CheckSchedule")] + public IActionResult Schedule() + { + return _generator.Generate("/ContosoCorp/Trains/Schedule"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs b/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs new file mode 100644 index 0000000000..832fb4d8ed --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs @@ -0,0 +1,52 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + public class BanksController : Controller + { + private readonly TestResponseGenerator _generator; + + public BanksController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet("Banks/[action]/{id}")] + [HttpGet("Bank/[action]/{id}")] + public ActionResult Get(int id) + { + return _generator.Generate( + Url.Action(), + Url.RouteUrl(new { })); + } + + [AcceptVerbs("PUT", Route = "Bank")] + [HttpPatch("Bank")] + [AcceptVerbs("PUT", Route = "Bank/Update")] + [HttpPatch("Bank/Update")] + public ActionResult UpdateBank() + { + return _generator.Generate( + Url.Action(), + Url.RouteUrl(new { })); + } + + [AcceptVerbs("PUT", "POST")] + [Route("Bank/Deposit")] + [Route("Bank/Deposit/{amount}")] + public ActionResult Deposit() + { + return _generator.Generate("/Bank/Deposit", "/Bank/Deposit/5"); + } + + [HttpPost] + [Route("Bank/Withdraw/{id}")] + public ActionResult Withdraw(int id) + { + return _generator.Generate("/Bank/Withdraw/5"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs b/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs new file mode 100644 index 0000000000..b48a9c05b4 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs @@ -0,0 +1,29 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + // This controller contains actions mapped with a single controller-level route. + [Route("Blog/[action]/{postId?}")] + public class BlogController + { + private readonly TestResponseGenerator _generator; + + public BlogController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult ShowPosts() + { + return _generator.Generate("/Blog/ShowPosts"); + } + + public IActionResult Edit(int postId) + { + return _generator.Generate("/Blog/Edit/" + postId); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs b/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs new file mode 100644 index 0000000000..cc45b934ef --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs @@ -0,0 +1,63 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + // A controller can define a route for all of the actions + // in it and give it a name for link generation purposes. + [Route("api/Company/{id}", Name = "Company")] + public class CompanyController : Controller + { + private readonly TestResponseGenerator _generator; + + public CompanyController(TestResponseGenerator generator) + { + _generator = generator; + } + + // An action with the same template will inherit the name + // from the controller. + [HttpGet] + public ActionResult Get(int id) + { + return _generator.Generate(Url.RouteUrl("Company", new { id = id })); + } + + // Multiple actions can have the same named route as long + // as for a given Name, all the actions have the same template. + // That is, there can't be two link generation entries with same + // name and different templates. + [HttpPut] + public ActionResult Put(int id) + { + return _generator.Generate(Url.RouteUrl("Company", new { id = id })); + } + + // Two actions can have the same template and each of them can have + // a different route name. That is, a given template can have multiple + // names associated with it. + [HttpDelete(Name = "RemoveCompany")] + public ActionResult Delete(int id) + { + return _generator.Generate(Url.RouteUrl("RemoveCompany", new { id = id })); + } + + // An action that defines a non empty template doesn't inherit the name + // from the route on the controller . + [HttpGet("Employees")] + public ActionResult GetEmployees(int id) + { + return _generator.Generate(Url.RouteUrl(new { id = id })); + } + + // An action that defines a non empty template doesn't inherit the name + // from the controller but can perfectly define its own name. + [HttpGet("Departments", Name = "Departments")] + public ActionResult GetDepartments(int id) + { + return _generator.Generate(Url.RouteUrl("Departments", new { id = id })); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs b/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs new file mode 100644 index 0000000000..fd946d4752 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs @@ -0,0 +1,73 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + // This controller combines routes on the controller with routes on actions in a REST + navigation property + // style. + [Route("api/Employee")] + public class EmployeeController : Controller + { + private readonly TestResponseGenerator _generator; + + public EmployeeController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult List() + { + return _generator.Generate("/api/Employee"); + } + + [AcceptVerbs("PUT", "PATCH")] + public IActionResult UpdateEmployee() + { + return _generator.Generate("/api/Employee"); + } + + [AcceptVerbs("PUT", "PATCH", Route = "Manager")] + public IActionResult UpdateManager() + { + return _generator.Generate("/api/Employee/Manager"); + } + + [HttpMerge("{id}")] + public IActionResult MergeEmployee(int id) + { + return _generator.Generate("/api/Employee/" + id); + } + + [HttpGet("{id}")] + public IActionResult Get(int id) + { + return _generator.Generate("/api/Employee/" + id); + } + + [HttpGet("{id}/Administrator")] + public IActionResult GetAdministrator(int id) + { + return _generator.Generate("/api/Employee/" + id + "/Administrator"); + } + + [HttpGet("~/Manager/{id}")] + public IActionResult GetManager(int id) + { + return _generator.Generate("/Manager/" + id); + } + + [HttpDelete("{id}/Administrator")] + public IActionResult DeleteAdministrator(int id) + { + return _generator.Generate("/api/Employee/" + id + "/Administrator"); + } + + [Route("{id}/Salary")] + public IActionResult Salary(int id) + { + return _generator.Generate("/api/Employee/" + id + "/Salary"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs b/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs new file mode 100644 index 0000000000..25c32c8c3f --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs @@ -0,0 +1,31 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + [Route("Friends")] + public class FriendsController : Controller + { + private readonly TestResponseGenerator _generator; + + public FriendsController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet] + [HttpGet("{id}")] + public IActionResult Get([FromRoute]string id) + { + return _generator.Generate(id == null ? "/Friends" : $"/Friends/{id}"); + } + + [HttpDelete] + public IActionResult Delete() + { + return _generator.Generate("/Friends"); + } + } +} diff --git a/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs b/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs new file mode 100644 index 0000000000..0b40b54c15 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs @@ -0,0 +1,39 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + // This controller is reachable via traditional routing. + public class HomeController : Controller + { + private readonly TestResponseGenerator _generator; + + public HomeController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Index() + { + return _generator.Generate("/", "/Home", "/Home/Index"); + } + + public IActionResult About() + { + // There are no urls that reach this action - it's hidden by an attribute route. + return _generator.Generate(); + } + + public IActionResult Contact() + { + return _generator.Generate("/Home/Contact"); + } + + public IActionResult OptionalPath(string path = "default") + { + return _generator.Generate("/Home/OptionalPath/" + path); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs b/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs new file mode 100644 index 0000000000..2b45d3de62 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs @@ -0,0 +1,53 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + [Route("api/v1/Maps", Name = "v1", Order = 1)] + [Route("api/v2/Maps")] + public class MapsController : Controller + { + private readonly TestResponseGenerator _generator; + + public MapsController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet] + public ActionResult Get() + { + // Multiple attribute routes with name and order. + // We will always generate v2 routes except when + // we explicitly use "v1" to generate a v1 route. + return _generator.Generate( + Url.Action(), + Url.RouteUrl("v1"), + Url.RouteUrl(new { })); + } + + [HttpPost("/api/v2/Maps")] + public ActionResult Post() + { + return _generator.Generate( + Url.Action(), + Url.RouteUrl(new { })); + } + + [HttpPut("{id}")] + [HttpPatch("PartialUpdate/{id}")] + public ActionResult Update(int id) + { + // We will generate "/api/v2/Maps/PartialUpdate/{id}" + // in both cases, v1 routes will be discarded due to their + // Order and for v2 routes PartialUpdate has higher precedence. + // api/v1/Maps/{id} and api/v2/Maps/{id} will only match on PUT. + // api/v1/Maps/PartialUpdate/{id} and api/v2/Maps/PartialUpdate/{id} will only match on PATCH. + return _generator.Generate( + Url.Action(), + Url.RouteUrl(new { })); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs b/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs new file mode 100644 index 0000000000..195d810af6 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs @@ -0,0 +1,36 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + [Route("Order/[action]/{orderId?}", Name = "Order_[action]")] + public class OrderController : Controller + { + private readonly TestResponseGenerator _generator; + + public OrderController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet] + public IActionResult Add(int orderId) + { + return _generator.Generate("/Order/Add/1"); + } + + [HttpPost] + public IActionResult Add() + { + return _generator.Generate("/Order/Add"); + } + + [HttpPut] + public IActionResult Edit(int orderId) + { + return _generator.Generate("/Order/Edit/1"); + } + } +} diff --git a/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs b/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs new file mode 100644 index 0000000000..a6b7e679a5 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs @@ -0,0 +1,43 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + // This controller contains only actions with individual attribute routes. + public class StoreController : Controller + { + private readonly TestResponseGenerator _generator; + + public StoreController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet("Store/Shop/Products")] + public IActionResult ListProducts() + { + return _generator.Generate("/Store/Shop/Products"); + } + + // Intentionally designed to conflict with HomeController#About. + [HttpGet("Home/About")] + public IActionResult About() + { + return _generator.Generate("/Home/About"); + } + + [Route("Store/Shop/Orders")] + public IActionResult Orders() + { + return _generator.Generate("/Store/Shop/Orders"); + } + + [HttpGet("Store/Shop/Orders")] + public IActionResult GetOrders() + { + return _generator.Generate("/Store/Shop/Orders"); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs b/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs new file mode 100644 index 0000000000..a5459b8e79 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs @@ -0,0 +1,72 @@ +// 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; + +namespace DispatchingWebSite.Controllers +{ + [Route("/Teams", Order = 1)] + public class TeamController : Controller + { + private readonly TestResponseGenerator _generator; + + public TeamController(TestResponseGenerator generator) + { + _generator = generator; + } + + [HttpGet("/Team/{teamId}", Order = 2)] + public ActionResult GetTeam(int teamId) + { + return _generator.Generate("/Team/" + teamId); + } + + [HttpGet("/Team/{teamId}")] + public ActionResult GetOrganization(int teamId) + { + return _generator.Generate("/Team/" + teamId); + } + + [HttpGet("")] + public ActionResult GetTeams() + { + return _generator.Generate("/Teams"); + } + + [HttpGet("", Order = 0)] + public ActionResult GetOrganizations() + { + return _generator.Generate("/Teams"); + } + + [HttpGet("/Club/{clubId?}")] + public ActionResult GetClub() + { + return Content(Url.Action(), "text/plain"); + } + + [HttpGet("/Organization/{clubId?}", Order = 1)] + public ActionResult GetClub(int clubId) + { + return Content(Url.Action(), "text/plain"); + } + + [HttpGet("AllTeams")] + public ActionResult GetAllTeams() + { + return Content(Url.Action(), "text/plain"); + } + + [HttpGet("AllOrganizations", Order = 0)] + public ActionResult GetAllTeams(int notRelevant) + { + return Content(Url.Action(), "text/plain"); + } + + [HttpGet("/TeamName/{*Name=DefaultName}/")] + public ActionResult GetTeam(string name) + { + return _generator.Generate("/TeamName/" + name); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj b/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj new file mode 100644 index 0000000000..df863f0a9c --- /dev/null +++ b/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj @@ -0,0 +1,15 @@ + + + + $(StandardTestWebsiteTfms) + + + + + + + + + + + diff --git a/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs b/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs new file mode 100644 index 0000000000..19477e4889 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace DispatchingWebSite +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public class HttpMergeAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider + { + private static readonly IEnumerable _supportedMethods = new[] { "MERGE" }; + + public HttpMergeAttribute(string template) + { + Template = template; + } + + public IEnumerable HttpMethods + { + get { return _supportedMethods; } + } + + /// + public string Template { get; private set; } + + /// + public int? Order { get; set; } + + /// + public string Name { get; set; } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Startup.cs b/test/WebSites/DispatchingWebSite/Startup.cs new file mode 100644 index 0000000000..f576ad29d3 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/Startup.cs @@ -0,0 +1,78 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Logging; + +namespace DispatchingWebSite +{ + public class Startup + { + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + services.AddDispatcher(); + + services.AddMvc(); + + services.AddScoped(); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDispatcher(); + + //app.UseMvc(routes => + //{ + // routes.MapAreaRoute( + // "flightRoute", + // "adminRoute", + // "{area:exists}/{controller}/{action}", + // new { controller = "Home", action = "Index" }, + // new { area = "Travel" }); + + // routes.MapRoute( + // "ActionAsMethod", + // "{controller}/{action}", + // defaults: new { controller = "Home", action = "Index" }); + + // routes.MapRoute( + // "RouteWithOptionalSegment", + // "{controller}/{action}/{path?}"); + //}); + + app.UseEndpoint(); + } + + public static void Main(string[] args) + { + var host = CreateWebHostBuilder(args) + .ConfigureLogging((hostingContext, logging) => + { + logging.AddConsole(); + }) + .Build(); + + host.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseKestrel() + .UseIISIntegration(); + } +} + diff --git a/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs b/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs new file mode 100644 index 0000000000..41f9455f66 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs @@ -0,0 +1,61 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace DispatchingWebSite +{ + // Generates a response based on the expected URL and action context + public class TestResponseGenerator + { + private readonly ActionContext _actionContext; + private readonly IUrlHelperFactory _urlHelperFactory; + + public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory) + { + _urlHelperFactory = urlHelperFactory; + + _actionContext = contextAccessor.ActionContext; + if (_actionContext == null) + { + throw new InvalidOperationException("ActionContext should not be null here."); + } + } + + public JsonResult Generate(params string[] expectedUrls) + { + var link = (string)null; + var query = _actionContext.HttpContext.Request.Query; + if (query.ContainsKey("link")) + { + var values = query + .Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller") + .ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]); + + var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext); + link = urlHelper.Action(query["link_action"], query["link_controller"], values); + } + + var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo; + + return new JsonResult(new + { + expectedUrls = expectedUrls, + actualUrl = _actionContext.HttpContext.Request.Path.Value, + routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name, + routeValues = new Dictionary(_actionContext.RouteData.Values), + + action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName, + controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName, + + link, + }); + } + } +} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/readme.md b/test/WebSites/DispatchingWebSite/readme.md new file mode 100644 index 0000000000..357e09a324 --- /dev/null +++ b/test/WebSites/DispatchingWebSite/readme.md @@ -0,0 +1,4 @@ +DispatchingWebSite +=== + +This web site illustrates how to use conventional and attribute dispatching. From 7eac72ae46f74008ff8481fcd1400fcb74419587 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 14 Jun 2018 10:29:00 -0700 Subject: [PATCH 053/316] Set 2.1 baselines --- .../baseline.netcore.json | 666 +- .../baseline.netcore.json | 694 +- .../baseline.netcore.json | 12813 ++++++---------- .../breakingchanges.netcore.json | 36 - .../baseline.netcore.json | 274 +- .../baseline.netcore.json | 967 +- .../baseline.netcore.json | 481 +- .../baseline.netcore.json | 316 +- .../baseline.netcore.json | 426 +- .../baseline.netcore.json | 2121 +-- .../baseline.netcore.json | 2395 +-- .../breakingchanges.netcore.json | 7 - .../baseline.netcore.json | 979 +- .../baseline.netcore.json | 495 +- .../baseline.netcore.json | 4637 +----- .../breakingchanges.netcore.json | 17 - .../baseline.netcore.json | 1148 +- .../breakingchanges.netcore.json | 7 - .../baseline.netcore.json | 2 +- 19 files changed, 10459 insertions(+), 18022 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json delete mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/breakingchanges.netcore.json delete mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/breakingchanges.netcore.json delete mode 100644 src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/breakingchanges.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json index 3aa89a7a76..a3ee847f2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Abstractions, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Abstractions, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.ActionContext", @@ -756,6 +756,37 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "GetBindingInfo", + "Parameters": [ + { + "Name": "attributes", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryApplyBindingInfo", + "Parameters": [ + { + "Name": "modelMetadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1388,6 +1419,24 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "CreateBinder", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "bindingInfo", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_BindingInfo", @@ -1418,6 +1467,15 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Services", + "Parameters": [], + "ReturnType": "System.IServiceProvider", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -2124,6 +2182,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_PropertyName", @@ -2669,6 +2743,72 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadataProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetMetadataForProperties", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForParameter", + "Parameters": [ + { + "Name": "parameter", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelPropertyCollection", "Visibility": "Public", @@ -2712,6 +2852,17 @@ "System.Collections.Generic.IReadOnlyDictionary" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Count", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IReadOnlyCollection>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Root", @@ -2757,17 +2908,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IReadOnlyCollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_Keys", @@ -2816,6 +2956,23 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "TryAddModelException", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "exception", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AddModelError", @@ -4183,6 +4340,118 @@ "System.Collections.Generic.IReadOnlyDictionary" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Count", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Clear", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Contains", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CopyTo", + "Parameters": [ + { + "Name": "array", + "Type": "System.Collections.Generic.KeyValuePair[]" + }, + { + "Name": "arrayIndex", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerator>", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerable>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Item", @@ -4219,28 +4488,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReadOnly", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_Keys", @@ -4263,22 +4510,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Add", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Add", @@ -4299,33 +4530,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Contains", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsKey", @@ -4342,53 +4546,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "CopyTo", - "Parameters": [ - { - "Name": "array", - "Type": "System.Collections.Generic.KeyValuePair[]" - }, - { - "Name": "arrayIndex", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetEnumerator", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerator>", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerable>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Remove", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Remove", @@ -4775,6 +4932,20 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "ForParameter", + "Parameters": [ + { + "Name": "parameter", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ContainerType", @@ -4807,6 +4978,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ParameterInfo", + "Parameters": [], + "ReturnType": "System.Reflection.ParameterInfo", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Equals", @@ -4871,6 +5050,13 @@ "Parameters": [], "GenericParameter": [], "Literal": "1" + }, + { + "Kind": "Field", + "Name": "Parameter", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" } ], "GenericParameters": [] @@ -4971,6 +5157,23 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext", "Visibility": "Public", @@ -5096,6 +5299,75 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterException", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Exception", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "message", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "message", + "Type": "System.String" + }, + { + "Name": "innerException", + "Type": "System.Exception" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "AllExceptions", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "MalformedInputExceptions", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterResult", "Visibility": "Public", @@ -5909,6 +6181,43 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "IsEffectivePolicy", + "Parameters": [ + { + "Name": "policy", + "Type": "T0" + } + ], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TMetadata", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, + { + "Kind": "Method", + "Name": "FindEffectivePolicy", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TMetadata", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ] + } + ] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -6180,6 +6489,17 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAlwaysRunResultFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" + ], + "Members": [], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter", "Visibility": "Public", @@ -6208,6 +6528,17 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAlwaysRunResultFilter", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter" + ], + "Members": [], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", "Visibility": "Public", @@ -7739,6 +8070,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_IsDefaultResponse", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsDefaultResponse", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8731,17 +9083,6 @@ "System.Collections.Generic.IEnumerator>" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "System.Collections.Generic.KeyValuePair", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -8775,6 +9116,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "System.Collections.Generic.KeyValuePair", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8835,17 +9187,6 @@ "System.Collections.Generic.IEnumerator" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -8879,6 +9220,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8939,17 +9291,6 @@ "System.Collections.Generic.IEnumerator" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -8983,6 +9324,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json index eb22f9fc45..798fc429d3 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.ApiExplorer, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.ApiExplorer, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionExtensions", @@ -249,6 +249,30 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "optionsAccessor", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "constraintResolver", + "Type": "Microsoft.AspNetCore.Routing.IInlineConstraintResolver" + }, + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "mapper", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultTypeMapper" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -296,6 +320,674 @@ } ], "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescription", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ActionDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ActionDescriptor", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_GroupName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_GroupName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HttpMethod", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HttpMethod", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterDescriptions", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RelativePath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RelativePath", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SupportedRequestFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SupportedResponseTypes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Actions", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Results", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actions", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterDescription", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_RouteInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_RouteInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Source", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Source", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Type", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterDescriptor", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ParameterDescriptor", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ParameterDescriptor" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiParameterRouteInfo", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Constraints", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Constraints", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_DefaultValue", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DefaultValue", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsOptional", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsOptional", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiRequestFormat", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Formatter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Formatter", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MediaType", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MediaType", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseFormat", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Formatter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Formatter", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MediaType", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MediaType", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiResponseType", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_ApiResponseFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ApiResponseFormats", + "Parameters": [ + { + "Name": "value", + "Type": "System.Collections.Generic.IList" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelMetadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ModelMetadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Type", + "Parameters": [ + { + "Name": "value", + "Type": "System.Type" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_StatusCode", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_StatusCode", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsDefaultResponse", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_IsDefaultResponse", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnProvidersExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiDescriptionProviderContext" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] } ] } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json index 3ce4e5517f..b0e0b7509f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Core, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Core, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Builder.MvcApplicationBuilderExtensions", @@ -760,6 +760,211 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ActionResult", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IConvertToActionResult" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Result", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Implicit", + "Parameters": [ + { + "Name": "value", + "Type": "T0" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "op_Implicit", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ActionResult" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "value", + "Type": "T0" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ActionResult" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TValue", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiBehaviorOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_InvalidModelStateResponseFactory", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_InvalidModelStateResponseFactory", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressModelStateInvalidFilter", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressModelStateInvalidFilter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressInferBindingSourcesForParameters", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressInferBindingSourcesForParameters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_SuppressConsumesConstraintForFormFileParameters", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressConsumesConstraintForFormFileParameters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApiControllerAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ControllerAttribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata" + ], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApiExplorerSettingsAttribute", "Visibility": "Public", @@ -967,6 +1172,44 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.BindPropertiesAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_SupportsGet", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SupportsGet", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.BindPropertyAttribute", "Visibility": "Public", @@ -978,6 +1221,16 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IRequestPredicateProvider" ], "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_SupportsGet", @@ -1023,16 +1276,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_BindingSource", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "set_BindingSource", @@ -1209,6 +1452,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_AuthenticationSchemes", @@ -1251,22 +1510,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1345,6 +1588,88 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.CompatibilityVersion", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "Version_2_0", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "Version_2_1", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "Latest", + "Parameters": [], + "GenericParameter": [], + "Literal": "2147483647" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ConflictObjectResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ConflictResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ConsumesAttribute", "Visibility": "Public", @@ -1356,6 +1681,22 @@ "Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestMetadataProvider" ], "Members": [ + { + "Kind": "Method", + "Name": "Accept", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ContentTypes", @@ -1409,22 +1750,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Accept", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "SetContentTypes", @@ -1478,6 +1803,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Content", @@ -1541,22 +1882,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1992,6 +2317,15 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "RedirectToAction", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RedirectToActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "RedirectToAction", @@ -2808,6 +3142,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2830,6 +3186,32 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2856,6 +3238,36 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2886,6 +3298,40 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileContents", + "Type": "System.Byte[]" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileContentResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2904,6 +3350,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2926,6 +3394,32 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2952,6 +3446,36 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -2982,6 +3506,40 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.FileStreamResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -3000,6 +3558,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -3022,6 +3602,32 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -3048,6 +3654,36 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "File", @@ -3078,6 +3714,40 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "File", + "Parameters": [ + { + "Name": "virtualPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.VirtualFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "PhysicalFile", @@ -3096,6 +3766,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "PhysicalFile", @@ -3118,6 +3810,32 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "PhysicalFile", @@ -3144,6 +3862,36 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "PhysicalFile", @@ -3174,6 +3922,40 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "PhysicalFile", + "Parameters": [ + { + "Name": "physicalPath", + "Type": "System.String" + }, + { + "Name": "contentType", + "Type": "System.String" + }, + { + "Name": "fileDownloadName", + "Type": "System.String" + }, + { + "Name": "lastModified", + "Type": "System.Nullable" + }, + { + "Name": "entityTag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.PhysicalFileResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Unauthorized", @@ -3243,6 +4025,117 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "UnprocessableEntity", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnprocessableEntityResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UnprocessableEntity", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UnprocessableEntity", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Conflict", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ConflictResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Conflict", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ConflictObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Conflict", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ConflictObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ValidationProblem", + "Parameters": [ + { + "Name": "descriptor", + "Type": "Microsoft.AspNetCore.Mvc.ValidationProblemDetails" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ValidationProblem", + "Parameters": [ + { + "Name": "modelStateDictionary", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ValidationProblem", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Created", @@ -4709,6 +5602,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.FileResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_FileContents", @@ -4730,22 +5639,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -4860,6 +5753,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_EnableRangeProcessing", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EnableRangeProcessing", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -4882,6 +5796,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.FileResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_FileStream", @@ -4903,22 +5833,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -5045,6 +5959,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_AuthenticationSchemes", @@ -5087,22 +6017,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -5732,6 +6646,17 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.IRequestFormLimitsPolicy", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" + ], + "Members": [], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.IRequestSizePolicy", "Visibility": "Public", @@ -5750,6 +6675,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Permanent", @@ -5910,6 +6851,16 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata" ], "Members": [ + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_BinderType", @@ -5934,16 +6885,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_BindingSource", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceMetadata", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "set_BindingSource", @@ -6038,7 +6979,9 @@ "Name": "Microsoft.AspNetCore.Mvc.MvcOptions", "Visibility": "Public", "Kind": "Class", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], "Members": [ { "Kind": "Method", @@ -6061,6 +7004,69 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_AllowCombiningAuthorizeFilters", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowCombiningAuthorizeFilters", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AllowBindingHeaderValuesToNonStringModelTypes", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowBindingHeaderValuesToNonStringModelTypes", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AllowValidatingTopLevelNodes", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowValidatingTopLevelNodes", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_CacheProfiles", @@ -6093,6 +7099,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_InputFormatterExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_InputFormatterExceptionPolicy", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_InputFormatters", @@ -6101,6 +7128,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_SuppressBindingUndefinedValueToEnumType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressBindingUndefinedValueToEnumType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_SuppressInputFormatterBuffering", @@ -6401,6 +7449,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Value", @@ -6506,22 +7570,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "OnFormatting", @@ -6597,6 +7645,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.FileResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_FileName", @@ -6618,22 +7682,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -6669,6 +7717,127 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ProblemDetails", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Type", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Title", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Title", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Status", + "Parameters": [], + "ReturnType": "System.Nullable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Status", + "Parameters": [ + { + "Name": "value", + "Type": "System.Nullable" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Detail", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Detail", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Instance", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Instance", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ProducesAttribute", "Visibility": "Public", @@ -6935,6 +8104,22 @@ "Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult" ], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Permanent", @@ -7094,6 +8279,22 @@ "Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult" ], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_UrlHelper", @@ -7424,6 +8625,22 @@ "Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult" ], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_UrlHelper", @@ -7840,6 +9057,22 @@ "Microsoft.AspNetCore.Mvc.ViewFeatures.IKeepTempDataResult" ], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_UrlHelper", @@ -8128,6 +9361,287 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.RequestFormLimitsAttribute", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Attribute", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Order", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BufferBody", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BufferBody", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MemoryBufferThreshold", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MemoryBufferThreshold", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BufferBodyLengthLimit", + "Parameters": [], + "ReturnType": "System.Int64", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BufferBodyLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueCountLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueCountLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_KeyLengthLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_KeyLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValueLengthLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValueLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MultipartBoundaryLengthLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MultipartBoundaryLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MultipartHeadersCountLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MultipartHeadersCountLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MultipartHeadersLengthLimit", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MultipartHeadersLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MultipartBodyLengthLimit", + "Parameters": [], + "ReturnType": "System.Int64", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MultipartBodyLengthLimit", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateInstance", + "Parameters": [ + { + "Name": "serviceProvider", + "Type": "System.IServiceProvider" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.RequestSizeLimitAttribute", "Visibility": "Public", @@ -8469,6 +9983,19 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "GetCacheProfile", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.CacheProfile", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "CreateInstance", @@ -8740,6 +10267,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_AuthenticationScheme", @@ -8803,22 +10346,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -8865,6 +10392,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_AuthenticationSchemes", @@ -8907,22 +10450,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -9169,6 +10696,57 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.UnprocessableEntityObjectResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.UnprocessableEntityResult", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.StatusCodeResult", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.UnsupportedMediaTypeResult", "Visibility": "Public", @@ -9761,6 +11339,43 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ValidationProblemDetails", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ProblemDetails", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Errors", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.VirtualFileResult", "Visibility": "Public", @@ -9768,6 +11383,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.FileResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_FileName", @@ -9810,22 +11441,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -10901,14 +12516,6 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider" ], "Members": [ - { - "Kind": "Method", - "Name": "get_BindingSource", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", - "Visibility": "Protected", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsPrefix", @@ -10941,6 +12548,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_BindingSource", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource", + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Filter", @@ -11014,9 +12629,40 @@ "BaseType": "System.Collections.ObjectModel.Collection", "ImplementedInterfaces": [ "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", - "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider" + "Microsoft.AspNetCore.Mvc.ModelBinding.IBindingSourceValueProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.IKeyRewriterValueProvider" ], "Members": [ + { + "Kind": "Method", + "Name": "ContainsPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "CreateAsync", @@ -11049,36 +12695,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ContainsPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetValue", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetKeysFromPrefix", @@ -11148,6 +12764,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IKeyRewriterValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -11249,22 +12876,6 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Culture", - "Parameters": [], - "ReturnType": "System.Globalization.CultureInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PrefixContainer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", - "Visibility": "Protected", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsPrefix", @@ -11281,21 +12892,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "GetKeysFromPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IDictionary", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetValue", @@ -11312,6 +12908,37 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Culture", + "Parameters": [], + "ReturnType": "System.Globalization.CultureInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PrefixContainer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetKeysFromPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Collections.Generic.IDictionary", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -11441,6 +13068,25 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IKeyRewriterValueProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory", "Visibility": "Public", @@ -11467,67 +13113,9 @@ "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProvider", "Visibility": "Public", "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider" - ], + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryValueProvider", + "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_PrefixContainer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ContainsPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetKeysFromPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetValue", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -11585,6 +13173,179 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryQueryStringValueProvider", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryValueProvider", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryQueryStringValueProviderFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProviderFactory" + ], + "Members": [ + { + "Kind": "Method", + "Name": "CreateValueProviderAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderFactoryContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProviderFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.JQueryValueProvider", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Microsoft.AspNetCore.Mvc.ModelBinding.IKeyRewriterValueProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ContainsPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Culture", + "Parameters": [], + "ReturnType": "System.Globalization.CultureInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PrefixContainer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetKeysFromPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Filter", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IKeyRewriterValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "bindingSource", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSource" + }, + { + "Name": "values", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", "Visibility": "Public", @@ -11607,6 +13368,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ParameterAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_TypeAttributes", @@ -11647,6 +13416,20 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "GetAttributesForParameter", + "Parameters": [ + { + "Name": "parameterInfo", + "Type": "System.Reflection.ParameterInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelAttributes", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -11717,6 +13500,26 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "serviceProvider", + "Type": "System.IServiceProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -11955,12 +13758,136 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ObjectModelValidator", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Validate", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "validationState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Validate", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "validationState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" + }, + { + "Name": "prefix", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValidationVisitor", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "validatorProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" + }, + { + "Name": "validatorCache", + "Type": "Microsoft.AspNetCore.Mvc.Internal.ValidatorCache" + }, + { + "Name": "metadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "validationState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "validatorProviders", + "Type": "System.Collections.Generic.IList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder", "Visibility": "Public", "Kind": "Class", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "BindModelAsync", @@ -12061,6 +13988,34 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "modelBinderFactory", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" + }, + { + "Name": "validator", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" + }, + { + "Name": "mvcOptions", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -12074,22 +14029,6 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Culture", - "Parameters": [], - "ReturnType": "System.Globalization.CultureInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PrefixContainer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", - "Visibility": "Protected", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsPrefix", @@ -12106,21 +14045,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "GetKeysFromPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IDictionary", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetValue", @@ -12137,6 +14061,37 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Culture", + "Parameters": [], + "ReturnType": "System.Globalization.CultureInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PrefixContainer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetKeysFromPrefix", + "Parameters": [ + { + "Name": "prefix", + "Type": "System.String" + } + ], + "ReturnType": "System.Collections.Generic.IDictionary", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -12201,22 +14156,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_PrefixContainer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Culture", - "Parameters": [], - "ReturnType": "System.Globalization.CultureInfo", - "Visibility": "Protected", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsPrefix", @@ -12249,6 +14188,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_PrefixContainer", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Culture", + "Parameters": [], + "ReturnType": "System.Globalization.CultureInfo", + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -12743,6 +14698,188 @@ "Kind": "Class", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "get_ValidatorProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MetadataProvider", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Cache", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.ValidatorCache", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Context", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidationState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_CurrentPath", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Internal.ValidationStack", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Container", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Container", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Key", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Model", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Metadata", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Metadata", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Strategy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Strategy", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ValidateComplexTypesIfChildValidationFails", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ValidateComplexTypesIfChildValidationFails", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Validate", @@ -12764,6 +14901,32 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Validate", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "alwaysValidateAtTopLevel", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "ValidateNode", @@ -12773,6 +14936,93 @@ "Visibility": "Protected", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Visit", + "Parameters": [ + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "VisitComplexType", + "Parameters": [ + { + "Name": "defaultStrategy", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "VisitSimpleType", + "Parameters": [], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "VisitChildren", + "Parameters": [ + { + "Name": "strategy", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SuppressValidation", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValidationEntry", + "Parameters": [ + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateEntry", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -13010,6 +15260,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ParameterAttributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_PropertyAttributes", @@ -13589,6 +15847,38 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "GetMetadataForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForProperties", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Attributes", @@ -14001,38 +16291,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "GetMetadataForType", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Type" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetMetadataForProperties", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Type" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -14084,10 +16342,41 @@ "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultModelMetadataProvider", "Visibility": "Public", "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - ], + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadataProvider", + "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "GetMetadataForType", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetMetadataForProperties", + "Parameters": [ + { + "Name": "modelType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_DetailsProvider", @@ -14106,31 +16395,16 @@ }, { "Kind": "Method", - "Name": "GetMetadataForProperties", + "Name": "GetMetadataForParameter", "Parameters": [ { - "Name": "modelType", - "Type": "System.Type" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetMetadataForType", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Type" + "Name": "parameter", + "Type": "System.Reflection.ParameterInfo" } ], "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata", "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider", + "Override": true, "Visibility": "Public", "GenericParameter": [] }, @@ -14176,6 +16450,20 @@ "Visibility": "Protected", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "CreateParameterDetails", + "Parameters": [ + { + "Name": "key", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ModelMetadataIdentity" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DefaultMetadataDetails", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -14305,6 +16593,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_DisplayFormatStringProvider", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DisplayFormatStringProvider", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_DisplayName", @@ -14347,6 +16656,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_EditFormatStringProvider", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_EditFormatStringProvider", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_EnumGroupedDisplayNamesAndValues", @@ -14515,6 +16845,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_NullDisplayTextProvider", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_NullDisplayTextProvider", + "Parameters": [ + { + "Name": "value", + "Type": "System.Func" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Order", @@ -15055,478 +17406,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Internal.ModelBindingHelper", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "TryUpdateModelAsync", - "Parameters": [ - { - "Name": "model", - "Type": "T0" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "valueProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - }, - { - "Name": "objectModelValidator", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "TryUpdateModelAsync", - "Parameters": [ - { - "Name": "model", - "Type": "T0" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "valueProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - }, - { - "Name": "objectModelValidator", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - }, - { - "Name": "includeExpressions", - "Type": "System.Linq.Expressions.Expression>[]", - "IsParams": true - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "TryUpdateModelAsync", - "Parameters": [ - { - "Name": "model", - "Type": "T0" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "valueProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - }, - { - "Name": "objectModelValidator", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - }, - { - "Name": "propertyFilter", - "Type": "System.Func" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "TryUpdateModelAsync", - "Parameters": [ - { - "Name": "model", - "Type": "System.Object" - }, - { - "Name": "modelType", - "Type": "System.Type" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "valueProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - }, - { - "Name": "objectModelValidator", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryUpdateModelAsync", - "Parameters": [ - { - "Name": "model", - "Type": "System.Object" - }, - { - "Name": "modelType", - "Type": "System.Type" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "valueProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - }, - { - "Name": "objectModelValidator", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - }, - { - "Name": "propertyFilter", - "Type": "System.Func" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetPropertyFilterExpression", - "Parameters": [ - { - "Name": "expressions", - "Type": "System.Linq.Expressions.Expression>[]" - } - ], - "ReturnType": "System.Linq.Expressions.Expression>", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "ClearValidationStateForModel", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Type" - }, - { - "Name": "modelState", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "modelKey", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ClearValidationStateForModel", - "Parameters": [ - { - "Name": "modelMetadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "modelState", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" - }, - { - "Name": "modelKey", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CanGetCompatibleCollection", - "Parameters": [ - { - "Name": "bindingContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" - } - ], - "ReturnType": "System.Boolean", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "GetCompatibleCollection", - "Parameters": [ - { - "Name": "bindingContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" - } - ], - "ReturnType": "System.Collections.Generic.ICollection", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "GetCompatibleCollection", - "Parameters": [ - { - "Name": "bindingContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" - }, - { - "Name": "capacity", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Collections.Generic.ICollection", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "ConvertTo", - "Parameters": [ - { - "Name": "value", - "Type": "System.Object" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "T0", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "ConvertTo", - "Parameters": [ - { - "Name": "value", - "Type": "System.Object" - }, - { - "Name": "type", - "Type": "System.Type" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "System.Object", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Internal.ValidationStack", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Push", - "Parameters": [ - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Pop", - "Parameters": [ - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ArrayModelBinder", "Visibility": "Public", @@ -15614,6 +17493,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "elementBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [ @@ -15765,7 +17660,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" } ], "Visibility": "Public", @@ -15781,7 +17676,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" }, { "Name": "loggerFactory", @@ -15801,7 +17696,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" }, { "Name": "loggerFactory", @@ -15852,7 +17747,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" } ], "Visibility": "Public", @@ -15868,7 +17763,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" }, { "Name": "loggerFactory", @@ -15888,7 +17783,7 @@ }, { "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory" }, { "Name": "loggerFactory", @@ -15935,6 +17830,18 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16049,14 +17956,6 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder" ], "Members": [ - { - "Kind": "Method", - "Name": "get_ElementBinder", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "Visibility": "Protected", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "BindModelAsync", @@ -16072,6 +17971,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ElementBinder", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "CanCreateInstance", @@ -16161,6 +18076,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "elementBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [ @@ -16312,6 +18243,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "propertyBinders", + "Type": "System.Collections.Generic.IDictionary" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16385,6 +18332,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16396,6 +18359,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.CollectionModelBinder>", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "CanCreateInstance", + "Parameters": [ + { + "Name": "targetType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "BindModelAsync", @@ -16447,18 +18426,18 @@ "GenericParameter": [] }, { - "Kind": "Method", - "Name": "CanCreateInstance", + "Kind": "Constructor", + "Name": ".ctor", "Parameters": [ { - "Name": "targetType", - "Type": "System.Type" + "Name": "keyBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "valueBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" } ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.ICollectionModelBinder", "Visibility": "Public", "GenericParameter": [] }, @@ -16473,6 +18452,10 @@ { "Name": "valueBinder", "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" } ], "Visibility": "Public", @@ -16561,6 +18544,114 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.EnumTypeModelBinder", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.SimpleTypeModelBinder", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CheckModel", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + }, + { + "Name": "valueProviderResult", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "suppressBindingUndefinedValueToEnumType", + "Type": "System.Boolean" + }, + { + "Name": "modelType", + "Type": "System.Type" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Binders.EnumTypeModelBinderProvider", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "GetBinder", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBinderProviderContext" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16634,6 +18725,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "supportedStyles", + "Type": "System.Globalization.NumberStyles" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16668,6 +18775,18 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16736,6 +18855,18 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16804,6 +18935,34 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "innerModelBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -16881,6 +19040,26 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "keyBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "valueBinder", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [ @@ -17006,6 +19185,28 @@ "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" ], "Members": [ + { + "Kind": "Method", + "Name": "CheckModel", + "Parameters": [ + { + "Name": "bindingContext", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" + }, + { + "Name": "valueProviderResult", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult" + }, + { + "Name": "model", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "BindModelAsync", @@ -17033,6 +19234,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -17071,6428 +19288,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ActionConstraintCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetActionConstraints", - "Parameters": [ - { - "Name": "httpContext", - "Type": "Microsoft.AspNetCore.Http.HttpContext" - }, - { - "Name": "action", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "collectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - }, - { - "Name": "actionConstraintProviders", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ActionDescriptorCollectionProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_ActionDescriptors", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Infrastructure.ActionDescriptorCollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionDescriptorProviders", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "actionDescriptorChangeProviders", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ActionInvokerFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateInvoker", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionInvokerProviders", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ActionSelector", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector" - ], - "Members": [ - { - "Kind": "Method", - "Name": "SelectCandidates", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.RouteContext" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectBestCandidate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.RouteContext" - }, - { - "Name": "candidates", - "Type": "System.Collections.Generic.IReadOnlyList" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectBestActions", - "Parameters": [ - { - "Name": "actions", - "Type": "System.Collections.Generic.IReadOnlyList" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionDescriptorCollectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - }, - { - "Name": "actionConstraintCache", - "Type": "Microsoft.AspNetCore.Mvc.Internal.ActionConstraintCache" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.AmbiguousActionException", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.InvalidOperationException", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "message", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "info", - "Type": "System.Runtime.Serialization.SerializationInfo" - }, - { - "Name": "context", - "Type": "System.Runtime.Serialization.StreamingContext" - } - ], - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ApiDescriptionActionData", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_GroupName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_GroupName", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ApplicationModelConventions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ApplyConventions", - "Parameters": [ - { - "Name": "applicationModel", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" - }, - { - "Name": "conventions", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.AttributeRoute", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Routing.IRouter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetVirtualPath", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.VirtualPathContext" - } - ], - "ReturnType": "Microsoft.AspNetCore.Routing.VirtualPathData", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RouteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.RouteContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionDescriptorCollectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - }, - { - "Name": "services", - "Type": "System.IServiceProvider" - }, - { - "Name": "handlerFactory", - "Type": "System.Func" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.AttributeRouting", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateAttributeMegaRoute", - "Parameters": [ - { - "Name": "services", - "Type": "System.IServiceProvider" - } - ], - "ReturnType": "Microsoft.AspNetCore.Routing.IRouter", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.AuthorizationApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFilter", - "Parameters": [ - { - "Name": "policyProvider", - "Type": "Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider" - }, - { - "Name": "authData", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "policyProvider", - "Type": "Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ClientValidatorCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetValidators", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "validatorProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ContentResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.ContentResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "httpResponseStreamWriterFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionDescriptorBuilder", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Build", - "Parameters": [ - { - "Name": "application", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AddRouteValues", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" - }, - { - "Name": "controller", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel" - }, - { - "Name": "action", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionDescriptorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionDescriptorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDescriptors", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BuildModel", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModel", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "partManager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - }, - { - "Name": "applicationModelProviders", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "optionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter", - "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Order", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnActionExecutionAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - }, - { - "Name": "next", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutionDelegate" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker" - ], - "Members": [ - { - "Kind": "Method", - "Name": "ReleaseResources", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "InvokeInnerFilterAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvokerCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetCachedResult", - "Parameters": [ - { - "Name": "controllerContext", - "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" - } - ], - "ReturnType": "System.ValueTuple", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "collectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - }, - { - "Name": "parameterBinder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "filterProviders", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "factoryProvider", - "Type": "Microsoft.AspNetCore.Mvc.Controllers.IControllerFactoryProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvokerCacheEntry", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_CachedFilters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ControllerFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ControllerReleaser", - "Parameters": [], - "ReturnType": "System.Action", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ControllerBinderDelegate", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegate", - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvokerProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "controllerActionInvokerCache", - "Type": "Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvokerCache" - }, - { - "Name": "optionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegate", - "Visibility": "Public", - "Kind": "Class", - "Sealed": true, - "BaseType": "System.MulticastDelegate", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Invoke", - "Parameters": [ - { - "Name": "controllerContext", - "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" - }, - { - "Name": "controller", - "Type": "System.Object" - }, - { - "Name": "arguments", - "Type": "System.Collections.Generic.Dictionary" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeginInvoke", - "Parameters": [ - { - "Name": "controllerContext", - "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" - }, - { - "Name": "controller", - "Type": "System.Object" - }, - { - "Name": "arguments", - "Type": "System.Collections.Generic.Dictionary" - }, - { - "Name": "callback", - "Type": "System.AsyncCallback" - }, - { - "Name": "object", - "Type": "System.Object" - } - ], - "ReturnType": "System.IAsyncResult", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EndInvoke", - "Parameters": [ - { - "Name": "result", - "Type": "System.IAsyncResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "object", - "Type": "System.Object" - }, - { - "Name": "method", - "Type": "System.IntPtr" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegateProvider", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateBinderDelegate", - "Parameters": [ - { - "Name": "parameterBinder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - }, - { - "Name": "modelBinderFactory", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderFactory" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.ControllerBinderDelegate", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ControllerResultFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter", - "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Order", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResultExecutionAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" - }, - { - "Name": "next", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutionDelegate" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.CopyOnWriteList", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "System.Collections.Generic.IList" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Readable", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Writable", - "Parameters": [], - "ReturnType": "System.Collections.Generic.List", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "index", - "Type": "System.Int32" - } - ], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Item", - "Parameters": [ - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "value", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReadOnly", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Add", - "Parameters": [ - { - "Name": "item", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Contains", - "Parameters": [ - { - "Name": "item", - "Type": "T0" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CopyTo", - "Parameters": [ - { - "Name": "array", - "Type": "T0[]" - }, - { - "Name": "arrayIndex", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetEnumerator", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerator", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "IndexOf", - "Parameters": [ - { - "Name": "item", - "Type": "T0" - } - ], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Insert", - "Parameters": [ - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "item", - "Type": "T0" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Remove", - "Parameters": [ - { - "Name": "item", - "Type": "T0" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RemoveAt", - "Parameters": [ - { - "Name": "index", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "source", - "Type": "System.Collections.Generic.IReadOnlyList" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultActionConstraintProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraintProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateControllerModel", - "Parameters": [ - { - "Name": "typeInfo", - "Type": "System.Reflection.TypeInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ControllerModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreatePropertyModel", - "Parameters": [ - { - "Name": "propertyInfo", - "Type": "System.Reflection.PropertyInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PropertyModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateActionModel", - "Parameters": [ - { - "Name": "typeInfo", - "Type": "System.Reflection.TypeInfo" - }, - { - "Name": "methodInfo", - "Type": "System.Reflection.MethodInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "IsAction", - "Parameters": [ - { - "Name": "typeInfo", - "Type": "System.Reflection.TypeInfo" - }, - { - "Name": "methodInfo", - "Type": "System.Reflection.MethodInfo" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateParameterModel", - "Parameters": [ - { - "Name": "parameterInfo", - "Type": "System.Reflection.ParameterInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "mvcOptionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultAssemblyPartDiscoveryProvider", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "DiscoverAssemblyParts", - "Parameters": [ - { - "Name": "entryPointAssemblyName", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultBindingMetadataProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateBindingMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultCollectionValidationStrategy", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetChildren", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerator", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetEnumeratorForElementType", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Collections.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Instance", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.DefaultCollectionValidationStrategy", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultComplexObjectValidationStrategy", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetChildren", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerator", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Instance", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultCompositeMetadataDetailsProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ICompositeMetadataDetailsProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateBindingMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateDisplayMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IDisplayMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateValidationMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "providers", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultControllerPropertyActivator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.IControllerPropertyActivator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Activate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" - }, - { - "Name": "controller", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IControllerPropertyActivator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetActivatorDelegate", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" - } - ], - "ReturnType": "System.Action", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IControllerPropertyActivator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultFilterProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ProvideFilter", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext" - }, - { - "Name": "filterItem", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterItem" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultModelValidatorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidators", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidatorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultObjectValidator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Validate", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "validationState", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationStateDictionary" - }, - { - "Name": "prefix", - "Type": "System.String" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IObjectModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "validatorProviders", - "Type": "System.Collections.Generic.IList" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DefaultValidationMetadataProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidationMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.DisableRequestSizeLimitResourceFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Microsoft.AspNetCore.Mvc.IRequestSizePolicy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Order", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResourceExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResourceExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ElementalValueProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Culture", - "Parameters": [], - "ReturnType": "System.Globalization.CultureInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Key", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Value", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ContainsPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetValue", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ValueProviderResult", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IValueProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.String" - }, - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ExplicitIndexCollectionValidationStrategy", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_ElementKeys", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetChildren", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerator", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "elementKeys", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FileContentResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.FileResultExecutorBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.FileContentResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteFileAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.FileContentResult" - }, - { - "Name": "range", - "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" - }, - { - "Name": "rangeLength", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FileResultExecutorBase", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SetHeadersAndLog", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.FileResult" - }, - { - "Name": "fileLength", - "Type": "System.Nullable" - }, - { - "Name": "lastModified", - "Type": "System.Nullable", - "DefaultValue": "default(System.Nullable)" - }, - { - "Name": "etag", - "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue", - "DefaultValue": "null" - }, - { - "Name": "enableRangeProcessing", - "Type": "System.Boolean", - "DefaultValue": "True" - } - ], - "ReturnType": "System.ValueTuple", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateLogger", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Static": true, - "Visibility": "Protected", - "GenericParameter": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "WriteFileAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Http.HttpContext" - }, - { - "Name": "fileStream", - "Type": "System.IO.Stream" - }, - { - "Name": "range", - "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" - }, - { - "Name": "rangeLength", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Static": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "BufferSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "Visibility": "Protected", - "GenericParameter": [], - "Constant": true, - "Literal": "65536" - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FileStreamResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.FileResultExecutorBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.FileStreamResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteFileAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.FileStreamResult" - }, - { - "Name": "range", - "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" - }, - { - "Name": "rangeLength", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FilterCursor", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Reset", - "Parameters": [], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetNextFilter", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.FilterCursorItem", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TFilter", - "ParameterPosition": 0, - "Class": true, - "BaseTypeOrInterfaces": [] - }, - { - "ParameterName": "TFilterAsync", - "ParameterPosition": 1, - "Class": true, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "filters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FilterCursorItem", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Filter", - "Parameters": [], - "ReturnType": "T0", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_FilterAsync", - "Parameters": [], - "ReturnType": "T1", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "filter", - "Type": "T0" - }, - { - "Name": "filterAsync", - "Type": "T1" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "TFilter", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - }, - { - "ParameterName": "TFilterAsync", - "ParameterPosition": 1, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FilterDescriptorOrderComparer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "System.Collections.Generic.IComparer" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Comparer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.FilterDescriptorOrderComparer", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Compare", - "Parameters": [ - { - "Name": "x", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor" - }, - { - "Name": "y", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterDescriptor" - } - ], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IComparer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FilterFactory", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetAllFilters", - "Parameters": [ - { - "Name": "filterProviders", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider[]" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.FilterFactoryResult", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateUncachedFilters", - "Parameters": [ - { - "Name": "filterProviders", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterProvider[]" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "cachedFilterItems", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.FilterFactoryResult", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_CacheableFilters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Filters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "cacheableFilters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]" - }, - { - "Name": "filters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_HttpMethods", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Accept", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "httpMethods", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "HttpMethodConstraintOrder", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IConsumesActionConstraint", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint" - ], - "Members": [], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IControllerPropertyActivator", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Activate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ControllerContext" - }, - { - "Name": "controller", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetActivatorDelegate", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Controllers.ControllerActionDescriptor" - } - ], - "ReturnType": "System.Action", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateReader", - "Parameters": [ - { - "Name": "stream", - "Type": "System.IO.Stream" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - } - ], - "ReturnType": "System.IO.TextReader", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateWriter", - "Parameters": [ - { - "Name": "stream", - "Type": "System.IO.Stream" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - } - ], - "ReturnType": "System.IO.TextWriter", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IMiddlewareFilterFeature", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ResourceExecutingContext", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ResourceExecutionDelegate", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutionDelegate", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.IResponseCacheFilter", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - ], - "Members": [], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ITypeActivatorCache", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateInstance", - "Parameters": [ - { - "Name": "serviceProvider", - "Type": "System.IServiceProvider" - }, - { - "Name": "optionType", - "Type": "System.Type" - } - ], - "ReturnType": "T0", - "GenericParameter": [ - { - "ParameterName": "TInstance", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.LocalRedirectResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Execute", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.LocalRedirectResult" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "urlHelperFactory", - "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MemoryPoolHttpRequestStreamReaderFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateReader", - "Parameters": [ - { - "Name": "stream", - "Type": "System.IO.Stream" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - } - ], - "ReturnType": "System.IO.TextReader", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "bytePool", - "Type": "System.Buffers.ArrayPool" - }, - { - "Name": "charPool", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "DefaultBufferSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MemoryPoolHttpResponseStreamWriterFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateWriter", - "Parameters": [ - { - "Name": "stream", - "Type": "System.IO.Stream" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - } - ], - "ReturnType": "System.IO.TextWriter", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "bytePool", - "Type": "System.Buffers.ArrayPool" - }, - { - "Name": "charPool", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "DefaultBufferSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MiddlewareFilterBuilder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ApplicationBuilder", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Builder.IApplicationBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ApplicationBuilder", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Builder.IApplicationBuilder" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetPipeline", - "Parameters": [ - { - "Name": "configurationType", - "Type": "System.Type" - } - ], - "ReturnType": "Microsoft.AspNetCore.Http.RequestDelegate", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "configurationProvider", - "Type": "Microsoft.AspNetCore.Mvc.Internal.MiddlewareFilterConfigurationProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MiddlewareFilterConfigurationProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateConfigureDelegate", - "Parameters": [ - { - "Name": "configurationType", - "Type": "System.Type" - } - ], - "ReturnType": "System.Action", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MiddlewareFilterFeature", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.IMiddlewareFilterFeature" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_ResourceExecutingContext", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IMiddlewareFilterFeature", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ResourceExecutingContext", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ResourceExecutionDelegate", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutionDelegate", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.IMiddlewareFilterFeature", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ResourceExecutionDelegate", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutionDelegate" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcAttributeRouteHandler", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Routing.IRouter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Actions", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Actions", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor[]" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetVirtualPath", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.VirtualPathContext" - } - ], - "ReturnType": "Microsoft.AspNetCore.Routing.VirtualPathData", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RouteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.RouteContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionInvokerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory" - }, - { - "Name": "actionSelector", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionInvokerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory" - }, - { - "Name": "actionSelector", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "actionContextAccessor", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcBuilder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.DependencyInjection.IMvcBuilder" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Services", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PartManager", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "manager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcCoreBuilder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_PartManager", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Services", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.DependencyInjection.IServiceCollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "manager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcCoreDiagnosticSourceExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BeforeAction", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" - }, - { - "Name": "httpContext", - "Type": "Microsoft.AspNetCore.Http.HttpContext" - }, - { - "Name": "routeData", - "Type": "Microsoft.AspNetCore.Routing.RouteData" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterAction", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionDescriptor" - }, - { - "Name": "httpContext", - "Type": "Microsoft.AspNetCore.Http.HttpContext" - }, - { - "Name": "routeData", - "Type": "Microsoft.AspNetCore.Routing.RouteData" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnAuthorizationAsync", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "authorizationContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnAuthorizationAsync", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "authorizationContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnAuthorization", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "authorizationContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnAuthorization", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "authorizationContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAuthorizationFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResourceExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResourceExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResourceExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResourceExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResourceExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResourceExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resourceExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnExceptionAsync", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "exceptionContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncExceptionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnExceptionAsync", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "exceptionContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncExceptionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnException", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "exceptionContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnException", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "exceptionContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ExceptionContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IExceptionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnActionExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnActionExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnActionExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnActionExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnActionExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnActionExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeActionMethod", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "actionArguments", - "Type": "System.Collections.Generic.IDictionary" - }, - { - "Name": "controller", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterActionMethod", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "actionArguments", - "Type": "System.Collections.Generic.IDictionary" - }, - { - "Name": "controller", - "Type": "System.Object" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.IActionResult" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResultExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResultExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResultExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResultExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnResultExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnResultExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "resultExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeActionResult", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.IActionResult" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterActionResult", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.IActionResult" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcCoreMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "readerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcCoreRouteOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Routing.RouteOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcMarkerService", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcRouteHandler", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Routing.IRouter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetVirtualPath", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.VirtualPathContext" - } - ], - "ReturnType": "Microsoft.AspNetCore.Routing.VirtualPathData", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RouteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Routing.RouteContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Routing.IRouter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionInvokerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory" - }, - { - "Name": "actionSelector", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionInvokerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionInvokerFactory" - }, - { - "Name": "actionSelector", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "actionContextAccessor", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.NonDisposableStream", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.IO.Stream", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_InnerStream", - "Parameters": [], - "ReturnType": "System.IO.Stream", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CanRead", - "Parameters": [], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CanSeek", - "Parameters": [], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CanWrite", - "Parameters": [], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Length", - "Parameters": [], - "ReturnType": "System.Int64", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Position", - "Parameters": [], - "ReturnType": "System.Int64", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Position", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ReadTimeout", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ReadTimeout", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CanTimeout", - "Parameters": [], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_WriteTimeout", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_WriteTimeout", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Seek", - "Parameters": [ - { - "Name": "offset", - "Type": "System.Int64" - }, - { - "Name": "origin", - "Type": "System.IO.SeekOrigin" - } - ], - "ReturnType": "System.Int64", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Read", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReadAsync", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - }, - { - "Name": "cancellationToken", - "Type": "System.Threading.CancellationToken" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeginRead", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - }, - { - "Name": "callback", - "Type": "System.AsyncCallback" - }, - { - "Name": "state", - "Type": "System.Object" - } - ], - "ReturnType": "System.IAsyncResult", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EndRead", - "Parameters": [ - { - "Name": "asyncResult", - "Type": "System.IAsyncResult" - } - ], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeginWrite", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - }, - { - "Name": "callback", - "Type": "System.AsyncCallback" - }, - { - "Name": "state", - "Type": "System.Object" - } - ], - "ReturnType": "System.IAsyncResult", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EndWrite", - "Parameters": [ - { - "Name": "asyncResult", - "Type": "System.IAsyncResult" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Close", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReadByte", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Flush", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CopyToAsync", - "Parameters": [ - { - "Name": "destination", - "Type": "System.IO.Stream" - }, - { - "Name": "bufferSize", - "Type": "System.Int32" - }, - { - "Name": "cancellationToken", - "Type": "System.Threading.CancellationToken" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FlushAsync", - "Parameters": [ - { - "Name": "cancellationToken", - "Type": "System.Threading.CancellationToken" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SetLength", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Byte[]" - }, - { - "Name": "offset", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - }, - { - "Name": "cancellationToken", - "Type": "System.Threading.CancellationToken" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteByte", - "Parameters": [ - { - "Name": "value", - "Type": "System.Byte" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Dispose", - "Parameters": [ - { - "Name": "disposing", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "innerStream", - "Type": "System.IO.Stream" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.NoOpBinder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" - ], - "Members": [ - { - "Kind": "Method", - "Name": "BindModelAsync", - "Parameters": [ - { - "Name": "bindingContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Instance", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.NormalizedRouteValue", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetNormalizedRouteValue", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ObjectResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_OptionsFormatters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.FormatterCollection", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_RespectBrowserAcceptHeader", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ReturnHttpNotAcceptable", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_WriterFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.ObjectResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectFormatter", - "Parameters": [ - { - "Name": "formatterContext", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "contentTypes", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectFormatterNotUsingContentType", - "Parameters": [ - { - "Name": "formatterContext", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectFormatterUsingSortedAcceptHeaders", - "Parameters": [ - { - "Name": "formatterContext", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "sortedAcceptHeaders", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectFormatterUsingAnyAcceptableContentType", - "Parameters": [ - { - "Name": "formatterContext", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "acceptableContentTypes", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectFormatterUsingSortedAcceptHeadersAndContentTypes", - "Parameters": [ - { - "Name": "formatterContext", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "sortedAcceptableContentTypes", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "possibleOutputContentTypes", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ParameterDefaultValues", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetParameterDefaultValues", - "Parameters": [ - { - "Name": "methodInfo", - "Type": "System.Reflection.MethodInfo" - } - ], - "ReturnType": "System.Object[]", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.PhysicalFileResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.FileResultExecutorBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.PhysicalFileResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteFileAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.PhysicalFileResult" - }, - { - "Name": "range", - "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" - }, - { - "Name": "rangeLength", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFileStream", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "System.IO.Stream", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFileInfo", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.PhysicalFileResultExecutor+FileMetadata", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.PlaceholderBinder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Inner", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Inner", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BindModelAsync", - "Parameters": [ - { - "Name": "bindingContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelBindingContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.PrefixContainer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ContainsPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetKeysFromPrefix", - "Parameters": [ - { - "Name": "prefix", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "values", - "Type": "System.Collections.Generic.ICollection" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.PropertyValueSetter", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "SetValue", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "instance", - "Type": "System.Object" - }, - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.RedirectResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Execute", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.RedirectResult" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "urlHelperFactory", - "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.RedirectToActionResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Execute", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.RedirectToActionResult" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "urlHelperFactory", - "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.RedirectToPageResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Execute", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.RedirectToPageResult" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "urlHelperFactory", - "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.RedirectToRouteResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Execute", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "urlHelperFactory", - "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.RequestSizeLimitResourceFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Microsoft.AspNetCore.Mvc.IRequestSizePolicy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Bytes", - "Parameters": [], - "ReturnType": "System.Int64", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Bytes", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResourceExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResourceExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "InvokeAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReleaseResources", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Abstract": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "InvokeInnerFilterAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Abstract": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "InvokeResultAsync", - "Parameters": [ - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.IActionResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "filters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]" - }, - { - "Name": "valueProviderFactories", - "Type": "System.Collections.Generic.IList" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_diagnosticSource", - "Parameters": [], - "ReturnType": "System.Diagnostics.DiagnosticSource", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_actionContext", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ActionContext", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_filters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_valueProviderFactories", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_cursor", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.FilterCursor", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_result", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_instance", - "Parameters": [], - "ReturnType": "System.Object", - "Visibility": "Protected", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ResponseCacheFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.IResponseCacheFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Duration", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Duration", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Location", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ResponseCacheLocation", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Location", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ResponseCacheLocation" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_NoStore", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_NoStore", - "Parameters": [ - { - "Name": "value", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_VaryByHeader", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_VaryByHeader", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_VaryByQueryKeys", - "Parameters": [], - "ReturnType": "System.String[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_VaryByQueryKeys", - "Parameters": [ - { - "Name": "value", - "Type": "System.String[]" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnActionExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnActionExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "cacheProfile", - "Type": "Microsoft.AspNetCore.Mvc.CacheProfile" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ResponseContentTypeHelper", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ResolveContentTypeAndEncoding", - "Parameters": [ - { - "Name": "actionResultContentType", - "Type": "System.String" - }, - { - "Name": "httpResponseContentType", - "Type": "System.String" - }, - { - "Name": "defaultContentType", - "Type": "System.String" - }, - { - "Name": "resolvedContentType", - "Type": "System.String", - "Direction": "Out" - }, - { - "Name": "resolvedContentTypeEncoding", - "Type": "System.Text.Encoding", - "Direction": "Out" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ShortFormDictionaryValidationStrategy", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_KeyMappings", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetChildren", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "model", - "Type": "System.Object" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerator", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "keyMappings", - "Type": "System.Collections.Generic.IEnumerable>" - }, - { - "Name": "valueMetadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "TKey", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - }, - { - "ParameterName": "TValue", - "ParameterPosition": 1, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.TypeActivatorCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Internal.ITypeActivatorCache" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateInstance", - "Parameters": [ - { - "Name": "serviceProvider", - "Type": "System.IServiceProvider" - }, - { - "Name": "implementationType", - "Type": "System.Type" - } - ], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Internal.ITypeActivatorCache", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TInstance", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ValidatorCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetValidators", - "Parameters": [ - { - "Name": "metadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "validatorProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.ViewEnginePath", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CombinePath", - "Parameters": [ - { - "Name": "first", - "Type": "System.String" - }, - { - "Name": "second", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ResolvePath", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.VirtualFileResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.FileResultExecutorBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.VirtualFileResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteFileAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.VirtualFileResult" - }, - { - "Name": "fileInfo", - "Type": "Microsoft.Extensions.FileProviders.IFileInfo" - }, - { - "Name": "range", - "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" - }, - { - "Name": "rangeLength", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFileStream", - "Parameters": [ - { - "Name": "fileInfo", - "Type": "Microsoft.Extensions.FileProviders.IFileInfo" - } - ], - "ReturnType": "System.IO.Stream", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "hostingEnvironment", - "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ActionContextAccessor", "Visibility": "Public", @@ -23579,6 +19374,525 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.CompatibilitySwitch", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsValueSet", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Value", + "Parameters": [ + { + "Name": "value", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "initialValue", + "Type": "T0" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TValue", + "ParameterPosition": 0, + "New": true, + "Struct": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ConfigureCompatibilityOptions", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.Extensions.Options.IPostConfigureOptions" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_DefaultValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", + "Virtual": true, + "Abstract": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Version", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.CompatibilityVersion", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "PostConfigure", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "options", + "Type": "T0" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.Extensions.Options.IPostConfigureOptions", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "compatibilityOptions", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TOptions", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [ + "System.Collections.Generic.IEnumerable" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ContentResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ContentResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "httpResponseStreamWriterFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultOutputFormatterSelector", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Infrastructure.OutputFormatterSelector", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "SelectFormatter", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + }, + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "contentTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.FileContentResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.FileContentResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteFileAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.FileContentResult" + }, + { + "Name": "range", + "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" + }, + { + "Name": "rangeLength", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetHeadersAndLog", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.FileResult" + }, + { + "Name": "fileLength", + "Type": "System.Nullable" + }, + { + "Name": "enableRangeProcessing", + "Type": "System.Boolean" + }, + { + "Name": "lastModified", + "Type": "System.Nullable", + "DefaultValue": "default(System.Nullable)" + }, + { + "Name": "etag", + "Type": "Microsoft.Net.Http.Headers.EntityTagHeaderValue", + "DefaultValue": "null" + } + ], + "ReturnType": "System.ValueTuple", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateLogger", + "Parameters": [ + { + "Name": "factory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Static": true, + "Visibility": "Protected", + "GenericParameter": [ + { + "ParameterName": "T", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Kind": "Method", + "Name": "WriteFileAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Http.HttpContext" + }, + { + "Name": "fileStream", + "Type": "System.IO.Stream" + }, + { + "Name": "range", + "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" + }, + { + "Name": "rangeLength", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Static": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Field", + "Name": "BufferSize", + "Parameters": [], + "ReturnType": "System.Int32", + "Static": true, + "Visibility": "Protected", + "GenericParameter": [], + "Constant": true, + "Literal": "65536" + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.FileStreamResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.FileStreamResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteFileAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.FileStreamResult" + }, + { + "Name": "range", + "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" + }, + { + "Name": "rangeLength", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionContextAccessor", "Visibility": "Public", @@ -23664,6 +19978,78 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "T0" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TResult", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [ + "Microsoft.AspNetCore.Mvc.IActionResult" + ] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultTypeMapper", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetResultDataType", + "Parameters": [ + { + "Name": "returnType", + "Type": "System.Type" + } + ], + "ReturnType": "System.Type", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Convert", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + }, + { + "Name": "returnType", + "Type": "System.Type" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionSelector", "Visibility": "Public", @@ -23702,6 +20088,766 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ICompatibilitySwitch", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_IsValueSet", + "Parameters": [], + "ReturnType": "System.Boolean", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Value", + "Parameters": [], + "ReturnType": "System.Object", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Value", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IConvertToActionResult", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Convert", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.IActionResult", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpRequestStreamReaderFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateReader", + "Parameters": [ + { + "Name": "stream", + "Type": "System.IO.Stream" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.IO.TextReader", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "CreateWriter", + "Parameters": [ + { + "Name": "stream", + "Type": "System.IO.Stream" + }, + { + "Name": "encoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.IO.TextWriter", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.LocalRedirectResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.LocalRedirectResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ModelStateInvalidFilter", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReusable", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnActionExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnActionExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "apiBehaviorOptions", + "Type": "Microsoft.AspNetCore.Mvc.ApiBehaviorOptions" + }, + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.MvcCompatibilityOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_CompatibilityVersion", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.CompatibilityVersion", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_CompatibilityVersion", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.CompatibilityVersion" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.ObjectResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_FormatterSelector", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Infrastructure.OutputFormatterSelector", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_WriterFactory", + "Parameters": [], + "ReturnType": "System.Func", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ObjectResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatterSelector", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.OutputFormatterSelector" + }, + { + "Name": "writerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.OutputFormatterSelector", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "SelectFormatter", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterCanWriteContext" + }, + { + "Name": "formatters", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "mediaTypes", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.MediaTypeCollection" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.PhysicalFileResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.PhysicalFileResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteFileAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.PhysicalFileResult" + }, + { + "Name": "range", + "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" + }, + { + "Name": "rangeLength", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetFileStream", + "Parameters": [ + { + "Name": "path", + "Type": "System.String" + } + ], + "ReturnType": "System.IO.Stream", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetFileInfo", + "Parameters": [ + { + "Name": "path", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Infrastructure.PhysicalFileResultExecutor+FileMetadata", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.RedirectResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.RedirectResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.RedirectToActionResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.RedirectToActionResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.RedirectToPageResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.RedirectToPageResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.RedirectToRouteResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.RedirectToRouteResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "urlHelperFactory", + "Type": "Microsoft.AspNetCore.Mvc.Routing.IUrlHelperFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.VirtualFileResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.Infrastructure.FileResultExecutorBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.VirtualFileResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteFileAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.VirtualFileResult" + }, + { + "Name": "fileInfo", + "Type": "Microsoft.Extensions.FileProviders.IFileInfo" + }, + { + "Name": "range", + "Type": "Microsoft.Net.Http.Headers.RangeItemHeaderValue" + }, + { + "Name": "rangeLength", + "Type": "System.Int64" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetFileStream", + "Parameters": [ + { + "Name": "fileInfo", + "Type": "Microsoft.Extensions.FileProviders.IFileInfo" + } + ], + "ReturnType": "System.IO.Stream", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "hostingEnvironment", + "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Formatters.FormatFilter", "Visibility": "Public", @@ -23802,6 +20948,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -24711,6 +21873,41 @@ "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatter", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "WriteResponseBodyAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "selectedEncoding", + "Type": "System.Text.Encoding" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WriteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_SupportedEncodings", @@ -24733,22 +21930,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IOutputFormatter", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "WriteResponseBodyAsync", @@ -24765,25 +21946,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "WriteResponseBodyAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" - }, - { - "Name": "selectedEncoding", - "Type": "System.Text.Encoding" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -24794,168 +21956,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Internal.AcceptHeaderParser", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ParseAcceptHeader", - "Parameters": [ - { - "Name": "acceptHeaders", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ParseAcceptHeader", - "Parameters": [ - { - "Name": "acceptHeaders", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "parsedValues", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Internal.HttpParseResult", - "Visibility": "Public", - "Kind": "Enumeration", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Field", - "Name": "Parsed", - "Parameters": [], - "GenericParameter": [], - "Literal": "0" - }, - { - "Kind": "Field", - "Name": "NotParsed", - "Parameters": [], - "GenericParameter": [], - "Literal": "1" - }, - { - "Kind": "Field", - "Name": "InvalidFormat", - "Parameters": [], - "GenericParameter": [], - "Literal": "2" - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Internal.HttpTokenParsingRules", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Internal.IFormatFilter", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetFormat", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.String", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Internal.MediaTypeSegmentWithQuality", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_MediaType", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Primitives.StringSegment", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Quality", - "Parameters": [], - "ReturnType": "System.Double", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ToString", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "mediaType", - "Type": "Microsoft.Extensions.Primitives.StringSegment" - }, - { - "Name": "quality", - "Type": "System.Double" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.Filters.ActionFilterAttribute", "Visibility": "Public", @@ -26273,6 +23273,13 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -26355,6 +23362,52 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory", + "Visibility": "Public", + "Kind": "Class", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetApplicationPartFactory", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager", "Visibility": "Public", @@ -26471,6 +23524,61 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.DefaultApplicationPartFactory", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Instance", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationParts.DefaultApplicationPartFactory", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetDefaultApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.IApplicationFeatureProvider", "Visibility": "Public", @@ -26548,6 +23656,130 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.NullApplicationPartFactory", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.ProvideApplicationPartFactoryAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetFactoryType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "factoryType", + "Type": "System.Type" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "factoryTypeName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.RelatedAssemblyAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_AssemblyFileName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetRelatedAssemblies", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + }, + { + "Name": "throwOnError", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assemblyFileName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ActionModel", "Visibility": "Public", @@ -26558,6 +23790,17 @@ "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ActionMethod", @@ -26675,20 +23918,17 @@ }, { "Kind": "Method", - "Name": "get_Properties", + "Name": "get_Selectors", "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "get_Selectors", + "Name": "get_DisplayName", "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", + "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, @@ -27146,6 +24386,17 @@ "Microsoft.AspNetCore.Mvc.ApplicationModels.IApiExplorerModel" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Actions", @@ -27271,20 +24522,17 @@ }, { "Kind": "Method", - "Name": "get_Properties", + "Name": "get_Selectors", "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "ReturnType": "System.Collections.Generic.IList", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "get_Selectors", + "Name": "get_DisplayName", "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", + "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, @@ -27534,6 +24782,28 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IParameterModelBaseConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "parameter", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IParameterModelConvention", "Visibility": "Public", @@ -27577,11 +24847,22 @@ "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModel", "Visibility": "Public", "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", - "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel" + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Action", @@ -27614,17 +24895,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_ParameterInfo", @@ -27656,28 +24926,9 @@ }, { "Kind": "Method", - "Name": "get_BindingInfo", + "Name": "get_DisplayName", "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_BindingInfo", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", + "ReturnType": "System.String", "Visibility": "Public", "GenericParameter": [] }, @@ -27713,14 +24964,139 @@ "GenericParameters": [] }, { - "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PropertyModel", + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", "Visibility": "Public", "Kind": "Class", + "Abstract": true, "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Attributes", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ParameterType", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_BindingInfo", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BindingInfo", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "parameterType", + "Type": "System.Type" + }, + { + "Name": "attributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "other", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase" + } + ], + "Visibility": "Protected", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PropertyModel", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Controller", @@ -27753,44 +25129,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_BindingInfo", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_BindingInfo", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_PropertyInfo", @@ -28242,6 +25580,25 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "conventions", + "Type": "System.Collections.Generic.IList" + }, + { + "Name": "parameterModelConvention", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.IParameterModelBaseConvention" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -28393,6 +25750,25 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetCompatibilityVersion", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "version", + "Type": "Microsoft.AspNetCore.Mvc.CompatibilityVersion" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -28545,6 +25921,25 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SetCompatibilityVersion", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "version", + "Type": "Microsoft.AspNetCore.Mvc.CompatibilityVersion" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -28596,7 +25991,76 @@ "GenericParameters": [] }, { - "Name": "Microsoft.AspNetCore.Mvc.Internal.PhysicalFileResultExecutor+FileMetadata", + "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor+StateManager", + "Visibility": "Protected", + "Kind": "Struct", + "Sealed": true, + "ImplementedInterfaces": [ + "System.IDisposable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Recurse", + "Parameters": [ + { + "Name": "visitor", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor" + }, + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "metadata", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" + }, + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "strategy", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IValidationStrategy" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor+StateManager", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "visitor", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ValidationVisitor" + }, + { + "Name": "newModel", + "Type": "System.Object" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Infrastructure.PhysicalFileResultExecutor+FileMetadata", "Visibility": "Protected", "Kind": "Class", "ImplementedInterfaces": [], @@ -28675,59 +26139,16 @@ "GenericParameters": [] }, { - "Name": "Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable+Awaiter", + "Name": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterException", "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [ - "System.Runtime.CompilerServices.ICriticalNotifyCompletion" - ], + "Kind": "Class", + "BaseType": "System.Exception", + "ImplementedInterfaces": [], "Members": [ { - "Kind": "Method", - "Name": "get_IsCompleted", + "Kind": "Constructor", + "Name": ".ctor", "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResult", - "Parameters": [], - "ReturnType": "System.Object", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnCompleted", - "Parameters": [ - { - "Name": "continuation", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Runtime.CompilerServices.INotifyCompletion", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "UnsafeOnCompleted", - "Parameters": [ - { - "Name": "continuation", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Runtime.CompilerServices.ICriticalNotifyCompletion", "Visibility": "Public", "GenericParameter": [] }, @@ -28736,24 +26157,24 @@ "Name": ".ctor", "Parameters": [ { - "Name": "customAwaiter", - "Type": "System.Object" + "Name": "message", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "message", + "Type": "System.String" }, { - "Name": "isCompletedMethod", - "Type": "System.Func" - }, - { - "Name": "getResultMethod", - "Type": "System.Func" - }, - { - "Name": "onCompletedMethod", - "Type": "System.Action" - }, - { - "Name": "unsafeOnCompletedMethod", - "Type": "System.Action" + "Name": "innerException", + "Type": "System.Exception" } ], "Visibility": "Public", diff --git a/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json deleted file mode 100644 index 9546877aea..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json +++ /dev/null @@ -1,36 +0,0 @@ -[ - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinder : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinder", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Mvc.MvcOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.Binders.BodyModelBinderProvider : Microsoft.AspNetCore.Mvc.ModelBinding.IModelBinderProvider", - "MemberId": "public .ctor(System.Collections.Generic.IList formatters, Microsoft.AspNetCore.Mvc.Internal.IHttpRequestStreamReaderFactory readerFactory, Microsoft.Extensions.Logging.ILoggerFactory loggerFactory, Microsoft.AspNetCore.Mvc.MvcOptions options)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ModelBinding.JQueryFormValueProvider : Microsoft.AspNetCore.Mvc.ModelBinding.BindingSourceValueProvider, Microsoft.AspNetCore.Mvc.ModelBinding.IEnumerableValueProvider", - "Kind": "Removal" - } -] diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json index 6f857b41c5..d00cbbb16b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Cors, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Cors, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.Extensions.DependencyInjection.MvcCorsMvcCoreBuilderExtensions", @@ -74,6 +74,33 @@ "Microsoft.AspNetCore.Mvc.Cors.Internal.ICorsAuthorizationFilter" ], "Members": [ + { + "Kind": "Method", + "Name": "OnAuthorizationAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Order", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_PolicyName", @@ -95,33 +122,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnAuthorizationAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -137,125 +137,22 @@ ], "Visibility": "Public", "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsAuthorizationFilterFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", - "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReusable", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateInstance", - "Parameters": [ - { - "Name": "serviceProvider", - "Type": "System.IServiceProvider" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", - "Visibility": "Public", - "GenericParameter": [] }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { - "Name": "policyName", - "Type": "System.String" + "Name": "corsService", + "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsService" + }, + { + "Name": "policyProvider", + "Type": "Microsoft.AspNetCore.Cors.Infrastructure.ICorsPolicyProvider" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" } ], "Visibility": "Public", @@ -263,101 +160,6 @@ } ], "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Accept", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionConstraints.ActionConstraintContext" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ActionConstraints.IActionConstraint", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "constraint", - "Type": "Microsoft.AspNetCore.Mvc.Internal.HttpMethodActionConstraint" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Cors.Internal.DisableCorsAuthorizationFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Cors.Internal.ICorsAuthorizationFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnAuthorizationAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Cors.Internal.ICorsAuthorizationFilter", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", - "Microsoft.AspNetCore.Mvc.Filters.IOrderedFilter" - ], - "Members": [], - "GenericParameters": [] } ] } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json index b2daa8e4ff..6ef863e5b3 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.DataAnnotations, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.DataAnnotations, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.HiddenInputAttribute", @@ -306,971 +306,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.CompareAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.CompareAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsClientModelValidatorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidators", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "stringLocalizerFactory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsLocalizationServices", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddDataAnnotationsLocalizationServices", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "setupAction", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsMetadataProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IDisplayMetadataProvider", - "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateBindingMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.BindingMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IBindingMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateDisplayMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DisplayMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IDisplayMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateValidationMetadata", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.ValidationMetadataProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.IValidationMetadataProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "stringLocalizerFactory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsModelValidator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Attribute", - "Parameters": [], - "ReturnType": "System.ComponentModel.DataAnnotations.ValidationAttribute", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Validate", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContext" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.ValidationAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataAnnotationsModelValidatorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidators", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidatorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidatorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "stringLocalizerFactory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DataTypeAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_RuleName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.DataTypeAttribute" - }, - { - "Name": "ruleName", - "Type": "System.String" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.DefaultClientModelValidatorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidators", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.MaxLengthAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.MaxLengthAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.MinLengthAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.MinLengthAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.MvcDataAnnotationsLocalizationOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.MvcDataAnnotationsLocalizationOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.MvcDataAnnotationsMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "dataAnnotationLocalizationOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "dataAnnotationLocalizationOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "stringLocalizerFactory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.NumericClientModelValidator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.NumericClientModelValidatorProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateValidators", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientValidatorProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidatorProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.RangeAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.RangeAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.RegularExpressionAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.RegularExpressionAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.RequiredAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.RequiredAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.StringLengthAttributeAdapter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.DataAnnotations.AttributeAdapterBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddValidation", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ClientModelValidationContext" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IClientModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetErrorMessage", - "Parameters": [ - { - "Name": "validationContext", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContextBase" - } - ], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.DataAnnotations.IAttributeAdapter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "attribute", - "Type": "System.ComponentModel.DataAnnotations.StringLengthAttribute" - }, - { - "Name": "stringLocalizer", - "Type": "Microsoft.Extensions.Localization.IStringLocalizer" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.DataAnnotations.Internal.ValidatableObjectAdapter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Validate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.ModelValidationContext" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ModelBinding.Validation.IModelValidator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcDataAnnotationsMvcBuilderExtensions", "Visibility": "Public", diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json index 46e30719ac..9a1c0bda85 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Json, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Json, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.JsonPatchExtensions", @@ -84,6 +84,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ContentType", @@ -168,22 +184,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -219,8 +219,31 @@ "Name": "Microsoft.AspNetCore.Mvc.MvcJsonOptions", "Visibility": "Public", "Kind": "Class", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], "Members": [ + { + "Kind": "Method", + "Name": "get_AllowInputFormatterExceptionMessages", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowInputFormatterExceptionMessages", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_SerializerSettings", @@ -244,8 +267,20 @@ "Visibility": "Public", "Kind": "Class", "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy" + ], "Members": [ + { + "Kind": "Method", + "Name": "get_ExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_SerializerSettings", @@ -347,6 +382,70 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + }, + { + "Name": "charPool", + "Type": "System.Buffers.ArrayPool" + }, + { + "Name": "objectPoolProvider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + }, + { + "Name": "suppressInputFormatterBuffering", + "Type": "System.Boolean" + }, + { + "Name": "allowInputFormatterExceptionMessages", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + }, + { + "Name": "charPool", + "Type": "System.Buffers.ArrayPool" + }, + { + "Name": "objectPoolProvider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + }, + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + }, + { + "Name": "jsonOptions", + "Type": "Microsoft.AspNetCore.Mvc.MvcJsonOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -459,6 +558,33 @@ "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.JsonInputFormatter", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "CanRead", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "ReadRequestBodyAsync", @@ -478,22 +604,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "CanRead", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterContext" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatter", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -545,6 +655,70 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + }, + { + "Name": "charPool", + "Type": "System.Buffers.ArrayPool" + }, + { + "Name": "objectPoolProvider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + }, + { + "Name": "suppressInputFormatterBuffering", + "Type": "System.Boolean" + }, + { + "Name": "allowInputFormatterExceptionMessages", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "logger", + "Type": "Microsoft.Extensions.Logging.ILogger" + }, + { + "Name": "serializerSettings", + "Type": "Newtonsoft.Json.JsonSerializerSettings" + }, + { + "Name": "charPool", + "Type": "System.Buffers.ArrayPool" + }, + { + "Name": "objectPoolProvider", + "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" + }, + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + }, + { + "Name": "jsonOptions", + "Type": "Microsoft.AspNetCore.Mvc.MvcJsonOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -636,243 +810,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.JsonArrayPool", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Newtonsoft.Json.IArrayPool" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Rent", - "Parameters": [ - { - "Name": "minimumLength", - "Type": "System.Int32" - } - ], - "ReturnType": "T0[]", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Newtonsoft.Json.IArrayPool", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "array", - "Type": "T0[]" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Newtonsoft.Json.IArrayPool", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "inner", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "T", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.JsonResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Options", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.MvcJsonOptions", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_WriterFactory", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.JsonResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "charPool", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.JsonSerializerObjectPolicy", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [], - "ReturnType": "Newtonsoft.Json.JsonSerializer", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "serializer", - "Type": "Newtonsoft.Json.JsonSerializer" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.ObjectPool.IPooledObjectPolicy", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "serializerSettings", - "Type": "Newtonsoft.Json.JsonSerializerSettings" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Json.Internal.MvcJsonMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "jsonOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "charPool", - "Type": "System.Buffers.ArrayPool" - }, - { - "Name": "objectPoolProvider", - "Type": "Microsoft.Extensions.ObjectPool.ObjectPoolProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcJsonMvcBuilderExtensions", "Visibility": "Public", diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json index 999ceb982b..e617a3e461 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Xml, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Formatters.Xml, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.ModelBinding.Metadata.DataMemberRequiredBindingMetadataProvider", @@ -40,7 +40,9 @@ "Visibility": "Public", "Kind": "Class", "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy" + ], "Members": [ { "Kind": "Method", @@ -100,6 +102,16 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "ReadRequestBodyAsync", @@ -212,6 +224,18 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -321,6 +345,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "CreateXmlWriter", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "writer", + "Type": "System.IO.TextWriter" + }, + { + "Name": "xmlWriterSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "ReturnType": "System.Xml.XmlWriter", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "WriteResponseBodyAsync", @@ -361,6 +407,18 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -372,6 +430,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "writerSettings", + "Type": "System.Xml.XmlWriterSettings" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -381,7 +455,9 @@ "Visibility": "Public", "Kind": "Class", "BaseType": "Microsoft.AspNetCore.Mvc.Formatters.TextInputFormatter", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy" + ], "Members": [ { "Kind": "Method", @@ -420,6 +496,16 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ExceptionPolicy", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Formatters.InputFormatterExceptionPolicy", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Formatters.IInputFormatterExceptionPolicy", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "ReadRequestBodyAsync", @@ -532,6 +618,18 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -620,6 +718,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "CreateXmlWriter", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Formatters.OutputFormatterWriteContext" + }, + { + "Name": "writer", + "Type": "System.IO.TextWriter" + }, + { + "Name": "xmlWriterSettings", + "Type": "System.Xml.XmlWriterSettings" + } + ], + "ReturnType": "System.Xml.XmlWriter", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "WriteResponseBodyAsync", @@ -639,6 +759,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Serialize", + "Parameters": [ + { + "Name": "xmlSerializer", + "Type": "System.Xml.Serialization.XmlSerializer" + }, + { + "Name": "xmlWriter", + "Type": "System.Xml.XmlWriter" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "GetCachedSerializer", @@ -660,6 +802,18 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -671,6 +825,22 @@ ], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "writerSettings", + "Type": "System.Xml.XmlWriterSettings" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -752,17 +922,6 @@ "System.Collections.Generic.IEnumerator" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -796,6 +955,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "T0", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1242,124 +1412,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal.FormattingUtilities", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetDefaultXmlReaderQuotas", - "Parameters": [], - "ReturnType": "System.Xml.XmlDictionaryReaderQuotas", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDefaultXmlWriterSettings", - "Parameters": [], - "ReturnType": "System.Xml.XmlWriterSettings", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "DefaultMaxDepth", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "XsdDataContractExporter", - "Parameters": [], - "ReturnType": "System.Runtime.Serialization.XsdDataContractExporter", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal.MvcXmlDataContractSerializerMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal.MvcXmlSerializerMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcXmlMvcBuilderExtensions", "Visibility": "Public", diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json index 07fa9302db..196aed9900 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Localization, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Localization, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.Localization.HtmlLocalizer", @@ -767,22 +767,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "WithCulture", - "Parameters": [ - { - "Name": "culture", - "Type": "System.Globalization.CultureInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetAllStrings", @@ -799,6 +783,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "WithCulture", + "Parameters": [ + { + "Name": "culture", + "Type": "System.Globalization.CultureInfo" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Localization.IHtmlLocalizer", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Contextualize", @@ -834,62 +834,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Localization.Internal.MvcLocalizationServices", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddLocalizationServices", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "format", - "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" - }, - { - "Name": "setupAction", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AddMvcLocalizationServices", - "Parameters": [ - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - }, - { - "Name": "format", - "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" - }, - { - "Name": "setupAction", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcLocalizationMvcBuilderExtensions", "Visibility": "Public", @@ -974,6 +918,174 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -1062,6 +1174,174 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddMvcLocalization", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder" + }, + { + "Name": "localizationOptionsSetupAction", + "Type": "System.Action" + }, + { + "Name": "format", + "Type": "Microsoft.AspNetCore.Mvc.Razor.LanguageViewLocationExpanderFormat" + }, + { + "Name": "dataAnnotationsLocalizationOptionsSetupAction", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.Extensions.DependencyInjection.IMvcCoreBuilder", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json index 8bb444c938..daae245f2e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Razor, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Razor, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.Razor.HelperResult", @@ -533,6 +533,17 @@ "BaseType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "EnsureRenderedBodyOrSections", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Context", @@ -663,17 +674,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "EnsureRenderedBodyOrSections", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "BeginContext", @@ -789,6 +789,68 @@ "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" ], "Members": [ + { + "Kind": "Method", + "Name": "EnsureRenderedBodyOrSections", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "DefineSection", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + }, + { + "Name": "section", + "Type": "Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BeginContext", + "Parameters": [ + { + "Name": "position", + "Type": "System.Int32" + }, + { + "Name": "length", + "Type": "System.Int32" + }, + { + "Name": "isLiteral", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "EndContext", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Abstract": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ViewContext", @@ -1175,24 +1237,6 @@ "Visibility": "Protected", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "DefineSection", - "Parameters": [ - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "section", - "Type": "Microsoft.AspNetCore.Mvc.Razor.RenderAsyncDelegate" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Write", @@ -1414,50 +1458,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "BeginContext", - "Parameters": [ - { - "Name": "position", - "Type": "System.Int32" - }, - { - "Name": "length", - "Type": "System.Int32" - }, - { - "Name": "isLiteral", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EndContext", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Abstract": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EnsureRenderedBodyOrSections", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Abstract": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1730,6 +1730,54 @@ "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine" ], "Members": [ + { + "Kind": "Method", + "Name": "FindView", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "viewName", + "Type": "System.String" + }, + { + "Name": "isMainPage", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetView", + "Parameters": [ + { + "Name": "executingFilePath", + "Type": "System.String" + }, + { + "Name": "viewPath", + "Type": "System.String" + }, + { + "Name": "isMainPage", + "Type": "System.Boolean" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ViewLookupCache", @@ -1796,54 +1844,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "FindView", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "viewName", - "Type": "System.String" - }, - { - "Name": "isMainPage", - "Type": "System.Boolean" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetView", - "Parameters": [ - { - "Name": "executingFilePath", - "Type": "System.String" - }, - { - "Name": "viewPath", - "Type": "System.String" - }, - { - "Name": "isMainPage", - "Type": "System.Boolean" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetAbsolutePath", @@ -1900,6 +1900,42 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pageFactory", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider" + }, + { + "Name": "pageActivator", + "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + }, + { + "Name": "optionsAccessor", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "razorFileSystem", + "Type": "Microsoft.AspNetCore.Razor.Language.RazorProjectFileSystem" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Field", "Name": "ViewExtension", @@ -1959,6 +1995,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_AreaPageViewLocationFormats", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IList", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_AdditionalCompilationReferences", @@ -2296,6 +2340,32 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentPropertyActivator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Activate", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + }, + { + "Name": "tagHelperComponent", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.TagHelperComponentTagHelper", "Visibility": "Public", @@ -2340,6 +2410,48 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_PropertyActivator", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentPropertyActivator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PropertyActivator", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentPropertyActivator" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -2595,1500 +2707,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.CSharpCompiler", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ParseOptions", - "Parameters": [], - "ReturnType": "Microsoft.CodeAnalysis.CSharp.CSharpParseOptions", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CSharpCompilationOptions", - "Parameters": [], - "ReturnType": "Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_EmitOptions", - "Parameters": [], - "ReturnType": "Microsoft.CodeAnalysis.Emit.EmitOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateSyntaxTree", - "Parameters": [ - { - "Name": "sourceText", - "Type": "Microsoft.CodeAnalysis.Text.SourceText" - } - ], - "ReturnType": "Microsoft.CodeAnalysis.SyntaxTree", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateCompilation", - "Parameters": [ - { - "Name": "assemblyName", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDependencyContextCompilationOptions", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.DependencyModel.CompilationOptions", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "manager", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorReferenceManager" - }, - { - "Name": "hostingEnvironment", - "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorPageFactoryProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateFactory", - "Parameters": [ - { - "Name": "relativePath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageFactoryResult", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewCompilerProvider", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompilerProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorReferenceManager", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorReferenceManager", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_CompilationReferences", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "partManager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - }, - { - "Name": "optionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorViewEngineFileProviderAccessor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_FileProvider", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "optionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultTagHelperActivator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TTagHelper", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [ - "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" - ] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "typeActivatorCache", - "Type": "Microsoft.AspNetCore.Mvc.Internal.ITypeActivatorCache" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultTagHelperFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.ITagHelperFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CreateTagHelper", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperFactory", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TTagHelper", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [ - "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" - ] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "activator", - "Type": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ExpressionRewriter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.CodeAnalysis.CSharp.CSharpSyntaxRewriter", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Rewrite", - "Parameters": [ - { - "Name": "compilation", - "Type": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation" - } - ], - "ReturnType": "Microsoft.CodeAnalysis.CSharp.CSharpCompilation", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "VisitClassDeclaration", - "Parameters": [ - { - "Name": "node", - "Type": "Microsoft.CodeAnalysis.CSharp.Syntax.ClassDeclarationSyntax" - } - ], - "ReturnType": "Microsoft.CodeAnalysis.SyntaxNode", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "VisitSimpleLambdaExpression", - "Parameters": [ - { - "Name": "node", - "Type": "Microsoft.CodeAnalysis.CSharp.Syntax.SimpleLambdaExpressionSyntax" - } - ], - "ReturnType": "Microsoft.CodeAnalysis.SyntaxNode", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "semanticModel", - "Type": "Microsoft.CodeAnalysis.SemanticModel" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.FileProviderRazorProject", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Razor.Language.RazorProject", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetItem", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Razor.Language.RazorProjectItem", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EnumerateItems", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "accessor", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.FileProviderRazorProjectItem", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Razor.Language.RazorProjectItem", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_FileInfo", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_BasePath", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_FilePath", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Exists", - "Parameters": [], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PhysicalPath", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Read", - "Parameters": [], - "ReturnType": "System.IO.Stream", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileInfo", - "Type": "Microsoft.Extensions.FileProviders.IFileInfo" - }, - { - "Name": "basePath", - "Type": "System.String" - }, - { - "Name": "path", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_FileProvider", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.LazyMetadataReferenceFeature", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.CodeAnalysis.Razor.IMetadataReferenceFeature" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_References", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.CodeAnalysis.Razor.IMetadataReferenceFeature", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Engine", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Razor.Language.RazorEngine", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorEngineFeature", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Engine", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorEngine" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.Language.IRazorEngineFeature", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "referenceManager", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorReferenceManager" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.MvcRazorDiagnosticSourceExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BeforeViewPage", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "page", - "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" - }, - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterViewPage", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "page", - "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage" - }, - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.MvcRazorLoggerExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "RazorFileToCodeCompilationStart", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "filePath", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RazorFileToCodeCompilationEnd", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "filePath", - "Type": "System.String" - }, - { - "Name": "startTimestamp", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ViewLookupCacheMiss", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "viewName", - "Type": "System.String" - }, - { - "Name": "controllerName", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ViewLookupCacheHit", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "viewName", - "Type": "System.String" - }, - { - "Name": "controllerName", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "PrecompiledViewFound", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "relativePath", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GeneratedCodeToAssemblyCompilationStart", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "filePath", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TagHelperComponentInitialized", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "componentName", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TagHelperComponentProcessed", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "componentName", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GeneratedCodeToAssemblyCompilationEnd", - "Parameters": [ - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "filePath", - "Type": "System.String" - }, - { - "Name": "startTimestamp", - "Type": "System.Int64" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.MvcRazorMvcViewOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcViewOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "razorViewEngine", - "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Attribute", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorPagePropertyActivator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Activate", - "Parameters": [ - { - "Name": "page", - "Type": "System.Object" - }, - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "pageType", - "Type": "System.Type" - }, - { - "Name": "modelType", - "Type": "System.Type" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "propertyValueAccessors", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorPagePropertyActivator+PropertyValueAccessors" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompiler" - ], - "Members": [ - { - "Kind": "Method", - "Name": "CompileAsync", - "Parameters": [ - { - "Name": "relativePath", - "Type": "System.String" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompiler", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CompileAndEmit", - "Parameters": [ - { - "Name": "relativePath", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompiledViewDescriptor", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileProvider", - "Type": "Microsoft.Extensions.FileProviders.IFileProvider" - }, - { - "Name": "templateEngine", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorTemplateEngine" - }, - { - "Name": "csharpCompiler", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.CSharpCompiler" - }, - { - "Name": "compilationCallback", - "Type": "System.Action" - }, - { - "Name": "precompiledViews", - "Type": "System.Collections.Generic.IList" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompilerProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompilerProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetCompiler", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompiler", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompilerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "applicationPartManager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - }, - { - "Name": "razorTemplateEngine", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorTemplateEngine" - }, - { - "Name": "fileProviderAccessor", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor" - }, - { - "Name": "csharpCompiler", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.CSharpCompiler" - }, - { - "Name": "viewEngineOptionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewEngineOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "hostingEnvironment", - "Type": "Microsoft.AspNetCore.Hosting.IHostingEnvironment" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ServiceBasedTagHelperActivator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Create", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "T0", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.ITagHelperActivator", - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TTagHelper", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [ - "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelper" - ] - } - ] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.TagHelperComponentManager", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Components", - "Parameters": [], - "ReturnType": "System.Collections.Generic.ICollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.TagHelpers.ITagHelperComponentManager", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "tagHelperComponents", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.TagHelpersAsServices", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddTagHelpersAsServices", - "Parameters": [ - { - "Name": "manager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - }, - { - "Name": "services", - "Type": "Microsoft.Extensions.DependencyInjection.IServiceCollection" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheItem", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Location", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PageFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "razorPageFactory", - "Type": "System.Func" - }, - { - "Name": "location", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheKey", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [ - "System.IEquatable" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_ViewName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ControllerName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_AreaName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PageName", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsMainPage", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ViewLocationExpanderValues", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Equals", - "Parameters": [ - { - "Name": "y", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheKey" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.IEquatable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Equals", - "Parameters": [ - { - "Name": "obj", - "Type": "System.Object" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetHashCode", - "Parameters": [], - "ReturnType": "System.Int32", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewName", - "Type": "System.String" - }, - { - "Name": "isMainPage", - "Type": "System.Boolean" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewName", - "Type": "System.String" - }, - { - "Name": "controllerName", - "Type": "System.String" - }, - { - "Name": "areaName", - "Type": "System.String" - }, - { - "Name": "pageName", - "Type": "System.String" - }, - { - "Name": "isMainPage", - "Type": "System.Boolean" - }, - { - "Name": "values", - "Type": "System.Collections.Generic.IReadOnlyDictionary" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheResult", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ViewEntry", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheItem", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ViewStartEntries", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_SearchedLocations", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Success", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "view", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewLocationCacheItem" - }, - { - "Name": "viewStarts", - "Type": "System.Collections.Generic.IReadOnlyList" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "searchedLocations", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.ViewPath", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "NormalizePath", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.Razor.Compilation.CompilationFailedException", "Visibility": "Public", @@ -4214,12 +2832,57 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItem", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Item", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItem" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Type", + "Parameters": [], + "ReturnType": "System.Type", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "item", + "Type": "Microsoft.AspNetCore.Razor.Hosting.RazorCompiledItem" + }, + { + "Name": "attribute", + "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.RazorViewAttribute" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -4522,6 +3185,111 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.CompiledRazorAssemblyApplicationPartFactory", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartFactory", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "GetDefaultApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Static": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetApplicationParts", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.CompiledRazorAssemblyPart", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPart", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationParts.IRazorCompiledItemProvider" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Assembly", + "Parameters": [], + "ReturnType": "System.Reflection.Assembly", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Virtual": true, + "Override": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "assembly", + "Type": "System.Reflection.Assembly" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationParts.IRazorCompiledItemProvider", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_CompiledItems", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcRazorMvcBuilderExtensions", "Visibility": "Public", @@ -4682,127 +3450,6 @@ } ], "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.Razor.Internal.RazorPagePropertyActivator+PropertyValueAccessors", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_UrlHelperAccessor", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_UrlHelperAccessor", - "Parameters": [ - { - "Name": "value", - "Type": "System.Func" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_JsonHelperAccessor", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_JsonHelperAccessor", - "Parameters": [ - { - "Name": "value", - "Type": "System.Func" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_DiagnosticSourceAccessor", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_DiagnosticSourceAccessor", - "Parameters": [ - { - "Name": "value", - "Type": "System.Func" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_HtmlEncoderAccessor", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_HtmlEncoderAccessor", - "Parameters": [ - { - "Name": "value", - "Type": "System.Func" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ModelExpressionProviderAccessor", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ModelExpressionProviderAccessor", - "Parameters": [ - { - "Name": "value", - "Type": "System.Func" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] } ] } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json index f09d520e8e..7a48f0e5f8 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json @@ -1,314 +1,6 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.RazorPages, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.RazorPages, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ - { - "Name": "Microsoft.AspNetCore.Mvc.Internal.MvcRazorPagesDiagnosticSourceExtensions", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BeforeHandlerMethod", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "handlerMethodDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" - }, - { - "Name": "arguments", - "Type": "System.Collections.Generic.IDictionary" - }, - { - "Name": "instance", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterHandlerMethod", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "handlerMethodDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" - }, - { - "Name": "arguments", - "Type": "System.Collections.Generic.IDictionary" - }, - { - "Name": "instance", - "Type": "System.Object" - }, - { - "Name": "result", - "Type": "Microsoft.AspNetCore.Mvc.IActionResult" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnPageHandlerExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutionContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnPageHandlerExecution", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnPageHandlerExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnPageHandlerExecuting", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutingContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnPageHandlerExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnPageHandlerExecuted", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerExecutedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnPageHandlerSelection", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerSelectedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnPageHandlerSelection", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerSelectedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BeforeOnPageHandlerSelected", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerSelectedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AfterOnPageHandlerSelected", - "Parameters": [ - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "handlerSelectedContext", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" - }, - { - "Name": "filter", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Extension": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", "Visibility": "Public", @@ -858,6 +550,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_DeclaredModelTypeInfo", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_DeclaredModelTypeInfo", + "Parameters": [ + { + "Name": "value", + "Type": "System.Reflection.TypeInfo" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ModelTypeInfo", @@ -1142,6 +855,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AreaName", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_DisplayName", @@ -1197,27 +931,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.Razor.RazorPageBase", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_PageContext", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_PageContext", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_ViewContext", @@ -1245,6 +958,38 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "EnsureRenderedBodyOrSections", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_PageContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_PageContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_HttpContext", @@ -1285,17 +1030,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "EnsureRenderedBodyOrSections", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Razor.IRazorPage", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "BeginContext", @@ -1329,6 +1063,43 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Challenge", @@ -3190,7 +2961,10 @@ "Visibility": "Public", "Kind": "Class", "Abstract": true, - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", + "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" + ], "Members": [ { "Kind": "Method", @@ -3555,6 +3329,43 @@ "Visibility": "Protected", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [ + { + "Name": "error", + "Type": "System.Object" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "BadRequest", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.BadRequestObjectResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Challenge", @@ -5058,6 +4869,85 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "OnPageHandlerSelected", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerExecuting", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerExecuted", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerSelectionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "OnPageHandlerExecutionAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" + }, + { + "Name": "next", + "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutionDelegate" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncPageFilter", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -5075,6 +4965,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ContentType", @@ -5167,22 +5073,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -5197,7 +5087,9 @@ "Name": "Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptions", "Visibility": "Public", "Kind": "Class", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], "Members": [ { "Kind": "Method", @@ -5228,1292 +5120,45 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.AuthorizationPageApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider" - ], - "Members": [ { "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "policyProvider", - "Type": "Microsoft.AspNetCore.Authorization.IAuthorizationPolicyProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.AutoValidateAntiforgeryPageApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.CompiledPageActionDescriptorBuilder", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Build", - "Parameters": [ - { - "Name": "applicationModel", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel" - }, - { - "Name": "globalFilters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterCollection" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.CompiledPageRouteModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetViewDescriptors", - "Parameters": [ - { - "Name": "applicationManager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "applicationManager", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager" - }, - { - "Name": "pagesOptionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.DefaultPageApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateModel", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" - }, - { - "Name": "pageTypeInfo", - "Type": "System.Reflection.TypeInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateHandlerModel", - "Parameters": [ - { - "Name": "method", - "Type": "System.Reflection.MethodInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateParameterModel", - "Parameters": [ - { - "Name": "parameter", - "Type": "System.Reflection.ParameterInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreatePropertyModel", - "Parameters": [ - { - "Name": "property", - "Type": "System.Reflection.PropertyInfo" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "IsHandler", - "Parameters": [ - { - "Name": "methodInfo", - "Type": "System.Reflection.MethodInfo" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.DefaultPageArgumentBinder", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageArgumentBinder", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BindAsync", - "Parameters": [ - { - "Name": "pageContext", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" - }, - { - "Name": "value", - "Type": "System.Object" - }, - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "type", - "Type": "System.Type" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "binder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.DefaultPageLoader", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Load", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "applicationModelProviders", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "viewCompilerProvider", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Compilation.IViewCompilerProvider" - }, - { - "Name": "pageOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "mvcOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.ExecutorFactory", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateExecutor", - "Parameters": [ - { - "Name": "handlerDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.HandlerMethodDescriptor" - } - ], - "ReturnType": "System.Func>", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionDescriptorChangeProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorChangeProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetChangeToken", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Primitives.IChangeToken", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorChangeProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "templateEngine", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorTemplateEngine" - }, - { - "Name": "fileProviderAccessor", - "Type": "Microsoft.AspNetCore.Mvc.Razor.Internal.IRazorViewEngineFileProviderAccessor" - }, - { - "Name": "razorPagesOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvoker", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvoker" - ], - "Members": [ - { - "Kind": "Method", - "Name": "InvokeInnerFilterAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReleaseResources", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "handlerMethodSelector", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageHandlerMethodSelector" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "logger", - "Type": "Microsoft.Extensions.Logging.ILogger" - }, - { - "Name": "pageContext", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageContext" - }, - { - "Name": "filterMetadata", - "Type": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata[]" - }, - { - "Name": "cacheEntry", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerCacheEntry" - }, - { - "Name": "parameterBinder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - }, - { - "Name": "tempDataFactory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - }, - { - "Name": "htmlHelperOptions", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelperOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerCacheEntry", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_ActionDescriptor", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PageFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ReleasePage", - "Parameters": [], - "ReturnType": "System.Action", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ModelFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ReleaseModel", - "Parameters": [], - "ReturnType": "System.Action", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PropertyBinder", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Executors", - "Parameters": [], - "ReturnType": "System.Func>[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ViewDataFactory", - "Parameters": [], - "ReturnType": "System.Func", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ViewStartFactories", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_CacheableFilters", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" - }, - { - "Name": "viewDataFactory", - "Type": "System.Func" - }, - { - "Name": "pageFactory", - "Type": "System.Func" - }, - { - "Name": "releasePage", - "Type": "System.Action" - }, - { - "Name": "modelFactory", - "Type": "System.Func" - }, - { - "Name": "releaseModel", - "Type": "System.Action" - }, - { - "Name": "propertyBinder", - "Type": "System.Func" - }, - { - "Name": "executors", - "Type": "System.Func>[]" - }, - { - "Name": "viewStartFactories", - "Type": "System.Collections.Generic.IReadOnlyList>" - }, - { - "Name": "cacheableFilters", - "Type": "Microsoft.AspNetCore.Mvc.Filters.FilterItem[]" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageActionInvokerProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Abstractions.ActionInvokerProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Abstractions.IActionInvokerProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "loader", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageLoader" - }, - { - "Name": "pageFactoryProvider", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.IPageFactoryProvider" - }, - { - "Name": "modelFactoryProvider", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.IPageModelFactoryProvider" - }, - { - "Name": "razorPageFactoryProvider", - "Type": "Microsoft.AspNetCore.Mvc.Razor.IRazorPageFactoryProvider" - }, - { - "Name": "collectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionDescriptorCollectionProvider" - }, - { - "Name": "filterProviders", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "parameterBinder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "tempDataFactory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - }, - { - "Name": "mvcOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "htmlHelperOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "selector", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.IPageHandlerMethodSelector" - }, - { - "Name": "razorProject", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorProject" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PagePropertyBinderFactory", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateBinder", - "Parameters": [ - { - "Name": "parameterBinder", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ParameterBinder" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "actionDescriptor", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.CompiledPageActionDescriptor" - } - ], - "ReturnType": "System.Func", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageSaveTempDataPropertyFilter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.SaveTempDataPropertyFilterBase", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IPageFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_FilterFactory", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageSaveTempDataPropertyFilterFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_FilterFactory", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageSaveTempDataPropertyFilterFactory" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnPageHandlerSelected", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerSelectedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnPageHandlerExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnPageHandlerExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.PageHandlerExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IPageFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageSaveTempDataPropertyFilterFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Properties", - "Parameters": [ - { - "Name": "value", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReusable", + "Name": "get_AllowAreas", "Parameters": [], "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "CreateInstance", + "Name": "set_AllowAreas", "Parameters": [ { - "Name": "serviceProvider", - "Type": "System.IServiceProvider" + "Name": "value", + "Type": "System.Boolean" } ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", + "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "GetTempDataProperties", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Type" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", + "Name": "get_AllowMappingHeadRequestsToGetHandler", "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.PageSelectorModel", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "PopulateDefaults", - "Parameters": [ - { - "Name": "model", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModel" - }, - { - "Name": "routeTemplate", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.RazorPagesRazorViewEngineOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.Razor.RazorViewEngineOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "pagesOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.RazorProjectPageRouteModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", + "ReturnType": "System.Boolean", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "OnProvidersExecuted", + "Name": "set_AllowMappingHeadRequestsToGetHandler", "Parameters": [ { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext" + "Name": "value", + "Type": "System.Boolean" } ], "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageRouteModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "razorProject", - "Type": "Microsoft.AspNetCore.Razor.Language.RazorProject" - }, - { - "Name": "pagesOptionsAccessor", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.RazorPages.Internal.TempDataFilterPageApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelProvider", "Visibility": "Public", "GenericParameter": [] }, @@ -7320,7 +5965,7 @@ "Parameters": [ { "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" }, { "Name": "compositeViewEngine", @@ -7689,6 +6334,18 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.Extensions.Options.IOptions" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -7767,6 +6424,30 @@ "Members": [], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageHandlerModelConvention", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageConvention" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Apply", + "Parameters": [ + { + "Name": "model", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageHandlerModel" + } + ], + "ReturnType": "System.Void", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelConvention", "Visibility": "Public", @@ -7862,6 +6543,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_RouteTemplate", @@ -7907,6 +6596,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_DeclaredModelType", + "Parameters": [], + "ReturnType": "System.Reflection.TypeInfo", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ModelType", @@ -7980,6 +6677,30 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "actionDescriptor", + "Type": "Microsoft.AspNetCore.Mvc.RazorPages.PageActionDescriptor" + }, + { + "Name": "declaredModelType", + "Type": "System.Reflection.TypeInfo" + }, + { + "Name": "handlerType", + "Type": "System.Reflection.TypeInfo" + }, + { + "Name": "handlerAttributes", + "Type": "System.Collections.Generic.IReadOnlyList" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8081,6 +6802,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AddAreaPageApplicationModelConvention", + "Parameters": [ + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AddFolderApplicationModelConvention", @@ -8098,6 +6840,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AddAreaFolderApplicationModelConvention", + "Parameters": [ + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "folderPath", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageApplicationModelConvention", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AddPageRouteModelConvention", @@ -8115,6 +6878,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AddAreaPageRouteModelConvention", + "Parameters": [ + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelConvention", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AddFolderRouteModelConvention", @@ -8132,6 +6916,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AddAreaFolderRouteModelConvention", + "Parameters": [ + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "folderPath", + "Type": "System.String" + }, + { + "Name": "action", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPageRouteModelConvention", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "RemoveType", @@ -8191,6 +6996,17 @@ "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Properties", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_MethodInfo", @@ -8305,17 +7121,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -8351,9 +7156,9 @@ "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageParameterModel", "Visibility": "Public", "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", - "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel" + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" ], "Members": [ { @@ -8377,28 +7182,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Attributes", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_ParameterInfo", @@ -8428,33 +7211,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_BindingInfo", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_BindingInfo", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -8490,9 +7246,9 @@ "Name": "Microsoft.AspNetCore.Mvc.ApplicationModels.PagePropertyModel", "Visibility": "Public", "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ApplicationModels.ParameterModelBase", "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", - "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel" + "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel" ], "Members": [ { @@ -8516,55 +7272,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Attributes", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.ICommonModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IPropertyModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_BindingInfo", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_BindingInfo", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.BindingInfo" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IBindingModel", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_PropertyInfo", @@ -8647,6 +7354,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_AreaName", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Properties", @@ -8663,6 +7378,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_RouteValues", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8679,6 +7402,26 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "relativePath", + "Type": "System.String" + }, + { + "Name": "viewEnginePath", + "Type": "System.String" + }, + { + "Name": "areaName", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -8914,6 +7657,29 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AllowAnonymousToAreaPage", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AllowAnonymousToFolder", @@ -8933,6 +7699,48 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "convention", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.IParameterModelBaseConvention" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AllowAnonymousToAreaFolder", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "folderPath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AuthorizePage", @@ -8975,6 +7783,56 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AuthorizeAreaPage", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AuthorizeAreaPage", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "policy", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AuthorizeFolder", @@ -9017,6 +7875,56 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "AuthorizeAreaFolder", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "folderPath", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AuthorizeAreaFolder", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "folderPath", + "Type": "System.String" + }, + { + "Name": "policy", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "AddPageRoute", @@ -9039,6 +7947,33 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddAreaPageRoute", + "Parameters": [ + { + "Name": "conventions", + "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection" + }, + { + "Name": "areaName", + "Type": "System.String" + }, + { + "Name": "pageName", + "Type": "System.String" + }, + { + "Name": "route", + "Type": "System.String" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ApplicationModels.PageConventionCollection", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Mvc.RazorPages/breakingchanges.netcore.json deleted file mode 100644 index d5d7030b72..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/breakingchanges.netcore.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageResultExecutor : Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "MemberId": "public .ctor(Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory writerFactory, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine compositeViewEngine, Microsoft.AspNetCore.Mvc.Razor.IRazorViewEngine razorViewEngine, Microsoft.AspNetCore.Mvc.Razor.IRazorPageActivator razorPageActivator, System.Diagnostics.DiagnosticSource diagnosticSource, System.Text.Encodings.Web.HtmlEncoder htmlEncoder)", - "Kind": "Removal" - } -] diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json index a18e83fff7..1de8718123 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.TagHelpers, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.TagHelpers, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.Rendering.ValidationSummary", @@ -330,6 +330,26 @@ "BaseType": "Microsoft.AspNetCore.Mvc.TagHelpers.CacheTagHelperBase", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ProcessAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_MemoryCache", @@ -359,26 +379,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ProcessAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" - }, - { - "Name": "output", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -735,6 +735,26 @@ "BaseType": "Microsoft.AspNetCore.Mvc.TagHelpers.CacheTagHelperBase", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ProcessAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_MemoryCache", @@ -764,26 +784,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ProcessAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" - }, - { - "Name": "output", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1708,6 +1708,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Value", @@ -1799,6 +1820,26 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "ProcessAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ViewContext", @@ -1849,26 +1890,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ProcessAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" - }, - { - "Name": "output", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -2225,6 +2246,26 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "ProcessAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Generator", @@ -2275,6 +2316,28 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "generator", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.PartialTagHelper", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelper", + "ImplementedInterfaces": [], + "Members": [ { "Kind": "Method", "Name": "ProcessAsync", @@ -2295,13 +2358,122 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_For", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_For", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExpression" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Model", + "Parameters": [], + "ReturnType": "System.Object", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Model", + "Parameters": [ + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewData", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", "Parameters": [ { - "Name": "generator", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.IHtmlGenerator" + "Name": "viewEngine", + "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine" + }, + { + "Name": "viewBufferScope", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope" } ], "Visibility": "Public", @@ -2328,27 +2500,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_ViewContext", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ViewContext", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Init", @@ -2385,6 +2536,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ViewContext", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_ViewContext", + "Parameters": [ + { + "Name": "value", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -2694,6 +2866,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "Init", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Generator", @@ -2767,17 +2955,22 @@ }, { "Kind": "Method", - "Name": "Init", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", "Parameters": [ { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + "Name": "value", + "Type": "System.String" } ], "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", "Visibility": "Public", "GenericParameter": [] }, @@ -2884,6 +3077,52 @@ "Extension": true, "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "AddClass", + "Parameters": [ + { + "Name": "tagHelperOutput", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + }, + { + "Name": "classValue", + "Type": "System.String" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "RemoveClass", + "Parameters": [ + { + "Name": "tagHelperOutput", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + }, + { + "Name": "classValue", + "Type": "System.String" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -2956,6 +3195,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Name", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Name", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Process", @@ -3008,6 +3268,26 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "ProcessAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" + }, + { + "Name": "output", + "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ViewContext", @@ -3058,26 +3338,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ProcessAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" - }, - { - "Name": "output", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperOutput" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Razor.TagHelpers.ITagHelperComponent", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -3195,505 +3455,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.AttributeMatcher", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "TryDetermineMode", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Razor.TagHelpers.TagHelperContext" - }, - { - "Name": "modeInfos", - "Type": "System.Collections.Generic.IReadOnlyList>" - }, - { - "Name": "compare", - "Type": "System.Func" - }, - { - "Name": "result", - "Type": "T0", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TMode", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.CacheTagHelperMemoryCacheFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Cache", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Caching.Memory.IMemoryCache", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.Extensions.Options.IOptions" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.CryptographyAlgorithms", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateSHA256", - "Parameters": [], - "ReturnType": "System.Security.Cryptography.SHA256", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.CurrentValues", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Values", - "Parameters": [], - "ReturnType": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ValuesAndEncodedValues", - "Parameters": [], - "ReturnType": "System.Collections.Generic.ICollection", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_ValuesAndEncodedValues", - "Parameters": [ - { - "Name": "value", - "Type": "System.Collections.Generic.ICollection" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "values", - "Type": "System.Collections.Generic.ICollection" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.FileProviderGlobbingDirectory", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_RelativePath", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_FullName", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Name", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ParentDirectory", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EnumerateFileSystemInfos", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetDirectory", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFile", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.FileInfoBase", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileProvider", - "Type": "Microsoft.Extensions.FileProviders.IFileProvider" - }, - { - "Name": "fileInfo", - "Type": "Microsoft.Extensions.FileProviders.IFileInfo" - }, - { - "Name": "parent", - "Type": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.FileProviderGlobbingDirectory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.FileProviderGlobbingFile", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.FileInfoBase", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_FullName", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Name", - "Parameters": [], - "ReturnType": "System.String", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_ParentDirectory", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileInfo", - "Type": "Microsoft.Extensions.FileProviders.IFileInfo" - }, - { - "Name": "parent", - "Type": "Microsoft.Extensions.FileSystemGlobbing.Abstractions.DirectoryInfoBase" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.FileVersionProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "AddFileVersionToPath", - "Parameters": [ - { - "Name": "path", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileProvider", - "Type": "Microsoft.Extensions.FileProviders.IFileProvider" - }, - { - "Name": "cache", - "Type": "Microsoft.Extensions.Caching.Memory.IMemoryCache" - }, - { - "Name": "requestPathBase", - "Type": "Microsoft.AspNetCore.Http.PathString" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.GlobbingUrlBuilder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Cache", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Caching.Memory.IMemoryCache", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_FileProvider", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.FileProviders.IFileProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_RequestPathBase", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Http.PathString", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "BuildUrlList", - "Parameters": [ - { - "Name": "staticUrl", - "Type": "System.String" - }, - { - "Name": "includePattern", - "Type": "System.String" - }, - { - "Name": "excludePattern", - "Type": "System.String" - } - ], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "fileProvider", - "Type": "Microsoft.Extensions.FileProviders.IFileProvider" - }, - { - "Name": "cache", - "Type": "Microsoft.Extensions.Caching.Memory.IMemoryCache" - }, - { - "Name": "requestPathBase", - "Type": "Microsoft.AspNetCore.Http.PathString" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.JavaScriptResources", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetEmbeddedJavaScript", - "Parameters": [ - { - "Name": "resourceName", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Internal.ModeAttributes", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Mode", - "Parameters": [], - "ReturnType": "T0", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Attributes", - "Parameters": [], - "ReturnType": "System.String[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "mode", - "Type": "T0" - }, - { - "Name": "attributes", - "Type": "System.String[]" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [ - { - "ParameterName": "TMode", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - } - ] - }, { "Name": "Microsoft.AspNetCore.Mvc.TagHelpers.Cache.CacheTagKey", "Visibility": "Public", diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json index 9e26dfeeb6..c0806560a2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json @@ -1 +1,494 @@ -{} \ No newline at end of file +{ + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.Testing, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "Types": [ + { + "Name": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.IDisposable" + ], + "Members": [ + { + "Kind": "Method", + "Name": "Finalize", + "Parameters": [], + "ReturnType": "System.Void", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Server", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.TestHost.TestServer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Factories", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ClientOptions", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "WithWebHostBuilder", + "Parameters": [ + { + "Name": "configuration", + "Type": "System.Action" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactory", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetTestAssemblies", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateWebHostBuilder", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Hosting.IWebHostBuilder", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateServer", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + } + ], + "ReturnType": "Microsoft.AspNetCore.TestHost.TestServer", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureWebHost", + "Parameters": [ + { + "Name": "builder", + "Type": "Microsoft.AspNetCore.Hosting.IWebHostBuilder" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateClient", + "Parameters": [], + "ReturnType": "System.Net.Http.HttpClient", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateClient", + "Parameters": [ + { + "Name": "options", + "Type": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions" + } + ], + "ReturnType": "System.Net.Http.HttpClient", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateDefaultClient", + "Parameters": [ + { + "Name": "handlers", + "Type": "System.Net.Http.DelegatingHandler[]", + "IsParams": true + } + ], + "ReturnType": "System.Net.Http.HttpClient", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ConfigureClient", + "Parameters": [ + { + "Name": "client", + "Type": "System.Net.Http.HttpClient" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CreateDefaultClient", + "Parameters": [ + { + "Name": "baseAddress", + "Type": "System.Uri" + }, + { + "Name": "handlers", + "Type": "System.Net.Http.DelegatingHandler[]", + "IsParams": true + } + ], + "ReturnType": "System.Net.Http.HttpClient", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.IDisposable", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Dispose", + "Parameters": [ + { + "Name": "disposing", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [ + { + "ParameterName": "TEntryPoint", + "ParameterPosition": 0, + "Class": true, + "BaseTypeOrInterfaces": [] + } + ] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryClientOptions", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_BaseAddress", + "Parameters": [], + "ReturnType": "System.Uri", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_BaseAddress", + "Parameters": [ + { + "Name": "value", + "Type": "System.Uri" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_AllowAutoRedirect", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_AllowAutoRedirect", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MaxAutomaticRedirections", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MaxAutomaticRedirections", + "Parameters": [ + { + "Name": "value", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_HandleCookies", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_HandleCookies", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Testing.WebApplicationFactoryContentRootAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentRootPath", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_ContentRootTest", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Priority", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "contentRootPath", + "Type": "System.String" + }, + { + "Name": "contentRootTest", + "Type": "System.String" + }, + { + "Name": "priority", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Testing.Handlers.CookieContainerHandler", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Net.Http.DelegatingHandler", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Container", + "Parameters": [], + "ReturnType": "System.Net.CookieContainer", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SendAsync", + "Parameters": [ + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "cancellationToken", + "Type": "System.Threading.CancellationToken" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "cookieContainer", + "Type": "System.Net.CookieContainer" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.Testing.Handlers.RedirectHandler", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "System.Net.Http.DelegatingHandler", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_MaxRedirects", + "Parameters": [], + "ReturnType": "System.Int32", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SendAsync", + "Parameters": [ + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "cancellationToken", + "Type": "System.Threading.CancellationToken" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "maxRedirects", + "Type": "System.Int32" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + } + ] +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json index 3307f2ac69..b3fca10e2b 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.ViewFeatures, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.ViewFeatures, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.AspNetCore.Mvc.AutoValidateAntiforgeryTokenAttribute", @@ -648,7 +648,9 @@ "Name": "Microsoft.AspNetCore.Mvc.MvcViewOptions", "Visibility": "Public", "Kind": "Class", - "ImplementedInterfaces": [], + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable" + ], "Members": [ { "Kind": "Method", @@ -671,6 +673,27 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_SuppressTempDataAttributePrefix", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_SuppressTempDataAttributePrefix", + "Parameters": [ + { + "Name": "value", + "Type": "System.Boolean" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_ViewEngines", @@ -704,6 +727,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_StatusCode", @@ -838,22 +877,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1147,6 +1170,27 @@ "BaseType": "System.Attribute", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Key", + "Parameters": [ + { + "Name": "value", + "Type": "System.String" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1509,6 +1553,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Arguments", @@ -1664,19 +1724,42 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "Microsoft.AspNetCore.Mvc.ViewDataAttribute", + "Visibility": "Public", + "Kind": "Class", + "Sealed": true, + "BaseType": "System.Attribute", + "ImplementedInterfaces": [], + "Members": [ { "Kind": "Method", - "Name": "ExecuteResultAsync", + "Name": "get_Key", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Key", "Parameters": [ { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + "Name": "value", + "Type": "System.String" } ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "ReturnType": "System.Void", "Visibility": "Public", "GenericParameter": [] }, @@ -1697,6 +1780,22 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ActionResult", "ImplementedInterfaces": [], "Members": [ + { + "Kind": "Method", + "Name": "ExecuteResultAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Override": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_StatusCode", @@ -1831,22 +1930,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "ExecuteResultAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.IActionResult", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -1865,17 +1948,6 @@ "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine" ], "Members": [ - { - "Kind": "Method", - "Name": "get_ViewEngines", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IReadOnlyList", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "FindView", @@ -1924,6 +1996,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ViewEngines", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IReadOnlyList", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -6493,6 +6576,33 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "RenderPartial", + "Parameters": [ + { + "Name": "htmlHelper", + "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" + }, + { + "Name": "partialViewName", + "Type": "System.String" + }, + { + "Name": "model", + "Type": "System.Object" + }, + { + "Name": "viewData", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "RenderPartialAsync", @@ -9229,6 +9339,66 @@ "Parameters": [], "Visibility": "Public", "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "text", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "text", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "selected", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "text", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.String" + }, + { + "Name": "selected", + "Type": "System.Boolean" + }, + { + "Name": "disabled", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] } ], "GenericParameters": [] @@ -9972,6 +10142,35 @@ } ] }, + { + "Kind": "Method", + "Name": "TryAddModelException", + "Parameters": [ + { + "Name": "modelState", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary" + }, + { + "Name": "expression", + "Type": "System.Linq.Expressions.Expression>" + }, + { + "Name": "exception", + "Type": "System.Exception" + } + ], + "ReturnType": "System.Void", + "Static": true, + "Extension": true, + "Visibility": "Public", + "GenericParameter": [ + { + "ParameterName": "TModel", + "ParameterPosition": 0, + "BaseTypeOrInterfaces": [] + } + ] + }, { "Kind": "Method", "Name": "AddModelError", @@ -10120,6 +10319,107 @@ "System.Collections.Generic.IReadOnlyDictionary" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Count", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Clear", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Contains", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CopyTo", + "Parameters": [ + { + "Name": "array", + "Type": "System.Collections.Generic.KeyValuePair[]" + }, + { + "Name": "arrayIndex", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Item", @@ -10156,28 +10456,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReadOnly", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_Keys", @@ -10200,33 +10478,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Add", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Add", @@ -10247,22 +10498,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Contains", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsKey", @@ -10279,26 +10514,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "CopyTo", - "Parameters": [ - { - "Name": "array", - "Type": "System.Collections.Generic.KeyValuePair[]" - }, - { - "Name": "arrayIndex", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "GetEnumerator", @@ -10307,22 +10522,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Remove", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Remove", @@ -13306,17 +13505,6 @@ "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" ], "Members": [ - { - "Kind": "Method", - "Name": "get_ViewData", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Contextualize", @@ -13333,6 +13521,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ViewData", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "CheckBoxFor", @@ -15437,6 +15636,121 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.PartialViewResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "FindView", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "viewResult", + "Type": "Microsoft.AspNetCore.Mvc.PartialViewResult" + } + ], + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "view", + "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.IView" + }, + { + "Name": "viewResult", + "Type": "Microsoft.AspNetCore.Mvc.PartialViewResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.PartialViewResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "viewOptions", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "writerFactory", + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" + }, + { + "Name": "viewEngine", + "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine" + }, + { + "Name": "tempDataFactory", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" + }, + { + "Name": "diagnosticSource", + "Type": "System.Diagnostics.DiagnosticSource" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.SaveTempDataAttribute", "Visibility": "Public", @@ -15611,39 +15925,6 @@ "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Keys", - "Parameters": [], - "ReturnType": "System.Collections.Generic.ICollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Values", - "Parameters": [], - "ReturnType": "System.Collections.Generic.ICollection", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_Item", @@ -15680,6 +15961,134 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Keys", + "Parameters": [], + "ReturnType": "System.Collections.Generic.ICollection", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Values", + "Parameters": [], + "ReturnType": "System.Collections.Generic.ICollection", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ContainsKey", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "TryGetValue", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + }, + { + "Name": "value", + "Type": "System.Object", + "Direction": "Out" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Count", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Clear", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerator>", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerable>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "Keep", @@ -15745,53 +16154,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Add", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ContainsKey", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ContainsValue", @@ -15805,54 +16167,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "GetEnumerator", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerator>", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerable>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Remove", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryGetValue", - "Parameters": [ - { - "Name": "key", - "Type": "System.String" - }, - { - "Name": "value", - "Type": "System.Object", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -16220,6 +16534,64 @@ ], "GenericParameters": [] }, + { + "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewComponentResultExecutor", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" + ], + "Members": [ + { + "Kind": "Method", + "Name": "ExecuteAsync", + "Parameters": [ + { + "Name": "context", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" + }, + { + "Name": "result", + "Type": "Microsoft.AspNetCore.Mvc.ViewComponentResult" + } + ], + "ReturnType": "System.Threading.Tasks.Task", + "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "mvcHelperOptions", + "Type": "Microsoft.Extensions.Options.IOptions" + }, + { + "Name": "loggerFactory", + "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + }, + { + "Name": "htmlEncoder", + "Type": "System.Text.Encodings.Web.HtmlEncoder" + }, + { + "Name": "modelMetadataProvider", + "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" + }, + { + "Name": "tempDataDictionaryFactory", + "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, { "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewContextAttribute", "Visibility": "Public", @@ -16245,6 +16617,107 @@ "System.Collections.Generic.IDictionary" ], "Members": [ + { + "Kind": "Method", + "Name": "get_Count", + "Parameters": [], + "ReturnType": "System.Int32", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IsReadOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Add", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Clear", + "Parameters": [], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Contains", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "CopyTo", + "Parameters": [ + { + "Name": "array", + "Type": "System.Collections.Generic.KeyValuePair[]" + }, + { + "Name": "arrayIndex", + "Type": "System.Int32" + } + ], + "ReturnType": "System.Void", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Remove", + "Parameters": [ + { + "Name": "item", + "Type": "System.Collections.Generic.KeyValuePair" + } + ], + "ReturnType": "System.Boolean", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.ICollection>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Method", "Name": "get_Model", @@ -16347,28 +16820,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReadOnly", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "get_Keys", @@ -16539,85 +16990,6 @@ "Visibility": "Public", "GenericParameter": [] }, - { - "Kind": "Method", - "Name": "Add", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Contains", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CopyTo", - "Parameters": [ - { - "Name": "array", - "Type": "System.Collections.Generic.KeyValuePair[]" - }, - { - "Name": "arrayIndex", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Remove", - "Parameters": [ - { - "Name": "item", - "Type": "System.Collections.Generic.KeyValuePair" - } - ], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.ICollection>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Constructor", "Name": ".ctor", @@ -17074,7 +17446,7 @@ "Kind": "Method", "Name": "get_WriterFactory", "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory", + "ReturnType": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory", "Visibility": "Protected", "GenericParameter": [] }, @@ -17143,7 +17515,7 @@ }, { "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" }, { "Name": "viewEngine", @@ -17171,7 +17543,7 @@ "Parameters": [ { "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" }, { "Name": "viewEngine", @@ -17199,1721 +17571,57 @@ "GenericParameters": [] }, { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ArrayPoolBufferSource", + "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor", "Visibility": "Public", "Kind": "Class", + "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource" + "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor" ], "Members": [ { "Kind": "Method", - "Name": "Rent", - "Parameters": [ - { - "Name": "bufferSize", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Char[]", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "pool", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.AutoValidateAntiforgeryTokenAuthorizationFilter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ShouldValidate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, + "Name": "get_Logger", + "Parameters": [], + "ReturnType": "Microsoft.Extensions.Logging.ILogger", "Visibility": "Protected", "GenericParameter": [] }, { - "Kind": "Constructor", - "Name": ".ctor", + "Kind": "Method", + "Name": "FindView", "Parameters": [ { - "Name": "antiforgery", - "Type": "Microsoft.AspNetCore.Antiforgery.IAntiforgery" + "Name": "actionContext", + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" }, { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" + "Name": "viewResult", + "Type": "Microsoft.AspNetCore.Mvc.ViewResult" } ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.CachedExpressionCompiler", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Process", - "Parameters": [ - { - "Name": "expression", - "Type": "System.Linq.Expressions.Expression>" - } - ], - "ReturnType": "System.Func", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - }, - { - "ParameterName": "TResult", - "ParameterPosition": 1, - "BaseTypeOrInterfaces": [] - } - ] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.CharArrayBufferSource", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Rent", - "Parameters": [ - { - "Name": "bufferSize", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Char[]", - "Sealed": true, + "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", "Visibility": "Public", "GenericParameter": [] }, { "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Instance", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.CharArrayBufferSource", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ControllerSaveTempDataPropertyFilter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.SaveTempDataPropertyFilterBase", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IActionFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "OnActionExecuted", + "Name": "ExecuteAsync", "Parameters": [ { "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnActionExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ActionExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IActionFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ControllerSaveTempDataPropertyFilterFactory", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_TempDataProperties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_TempDataProperties", - "Parameters": [ - { - "Name": "value", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsReusable", - "Parameters": [], - "ReturnType": "System.Boolean", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateInstance", - "Parameters": [ - { - "Name": "serviceProvider", - "Type": "System.IServiceProvider" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IFilterFactory", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DefaultDisplayTemplates", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BooleanTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CollectionTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "DecimalTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EmailAddressTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "HiddenInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "HtmlTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ObjectTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "StringTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "UrlTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DefaultEditorTemplates", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "BooleanTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CollectionTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "DecimalTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "HiddenInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "MultilineTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ObjectTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "PasswordTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "StringTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "PhoneNumberInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "UrlInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EmailAddressInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "DateTimeOffsetTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "DateTimeLocalInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "DateInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TimeInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "NumberInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FileInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FileCollectionInputTemplate", - "Parameters": [ - { - "Name": "htmlHelper", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.DynamicViewData", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.Dynamic.DynamicObject", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetDynamicMemberNames", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TryGetMember", - "Parameters": [ - { - "Name": "binder", - "Type": "System.Dynamic.GetMemberBinder" + "Type": "Microsoft.AspNetCore.Mvc.ActionContext" }, { "Name": "result", - "Type": "System.Object", - "Direction": "Out" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "TrySetMember", - "Parameters": [ - { - "Name": "binder", - "Type": "System.Dynamic.SetMemberBinder" - }, - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewDataFunc", - "Type": "System.Func" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionHelper", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetExpressionText", - "Parameters": [ - { - "Name": "expression", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetExpressionText", - "Parameters": [ - { - "Name": "expression", - "Type": "System.Linq.Expressions.LambdaExpression" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetExpressionText", - "Parameters": [ - { - "Name": "expression", - "Type": "System.Linq.Expressions.LambdaExpression" - }, - { - "Name": "expressionTextCache", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionTextCache" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "IsSingleArgumentIndexer", - "Parameters": [ - { - "Name": "expression", - "Type": "System.Linq.Expressions.Expression" - } - ], - "ReturnType": "System.Boolean", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionMetadataProvider", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "FromLambdaExpression", - "Parameters": [ - { - "Name": "expression", - "Type": "System.Linq.Expressions.Expression>" - }, - { - "Name": "viewData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer", - "Static": true, - "Visibility": "Public", - "GenericParameter": [ - { - "ParameterName": "TModel", - "ParameterPosition": 0, - "BaseTypeOrInterfaces": [] - }, - { - "ParameterName": "TResult", - "ParameterPosition": 1, - "BaseTypeOrInterfaces": [] - } - ] - }, - { - "Kind": "Method", - "Name": "FromStringExpression", - "Parameters": [ - { - "Name": "expression", - "Type": "System.String" - }, - { - "Name": "viewData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" - }, - { - "Name": "metadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ExpressionTextCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Entries", - "Parameters": [], - "ReturnType": "System.Collections.Concurrent.ConcurrentDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Rent", - "Parameters": [ - { - "Name": "bufferSize", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Char[]", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Return", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - } - ], - "ReturnType": "System.Void", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ISaveTempDataCallback", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata" - ], - "Members": [ - { - "Kind": "Method", - "Name": "OnTempDataSaving", - "Parameters": [ - { - "Name": "tempData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary" - } - ], - "ReturnType": "System.Void", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetPage", - "Parameters": [ - { - "Name": "pageSize", - "Type": "System.Int32" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReturnSegment", - "Parameters": [ - { - "Name": "segment", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]" - } - ], - "ReturnType": "System.Void", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateWriter", - "Parameters": [ - { - "Name": "writer", - "Type": "System.IO.TextWriter" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.PagedBufferedTextWriter", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.MemoryPoolViewBufferScope", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope", - "System.IDisposable" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetPage", - "Parameters": [ - { - "Name": "pageSize", - "Type": "System.Int32" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ReturnSegment", - "Parameters": [ - { - "Name": "segment", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateWriter", - "Parameters": [ - { - "Name": "writer", - "Type": "System.IO.TextWriter" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.PagedBufferedTextWriter", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Dispose", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.IDisposable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewBufferPool", - "Type": "System.Buffers.ArrayPool" - }, - { - "Name": "charPool", - "Type": "System.Buffers.ArrayPool" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "MinimumSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.MvcViewOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcViewOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "dataAnnotationLocalizationOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "dataAnnotationOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "validationAttributeAdapterProvider", - "Type": "Microsoft.AspNetCore.Mvc.DataAnnotations.IValidationAttributeAdapterProvider" - }, - { - "Name": "stringLocalizerFactory", - "Type": "Microsoft.Extensions.Localization.IStringLocalizerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NameAndIdProvider", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateSanitizedId", - "Parameters": [ - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - }, - { - "Name": "fullName", - "Type": "System.String" - }, - { - "Name": "invalidCharReplacement", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GenerateId", - "Parameters": [ - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - }, - { - "Name": "tagBuilder", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.TagBuilder" - }, - { - "Name": "fullName", - "Type": "System.String" - }, - { - "Name": "invalidCharReplacement", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetFullHtmlFieldName", - "Parameters": [ - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - }, - { - "Name": "expression", - "Type": "System.String" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NullView", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ViewEngines.IView" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Path", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IView", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "RenderAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" + "Type": "Microsoft.AspNetCore.Mvc.ViewResult" } ], "ReturnType": "System.Threading.Tasks.Task", "Sealed": true, "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewEngines.IView", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Instance", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.NullView", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.PagedBufferedTextWriter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.IO.TextWriter", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Encoding", - "Parameters": [], - "ReturnType": "System.Text.Encoding", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Flush", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FlushAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - }, - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - }, - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Dispose", - "Parameters": [ - { - "Name": "disposing", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "pool", - "Type": "System.Buffers.ArrayPool" - }, - { - "Name": "inner", - "Type": "System.IO.TextWriter" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.PagedCharBuffer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "System.IDisposable" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_BufferSource", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Pages", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Length", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Append", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Append", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Append", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - }, - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Dispose", - "Parameters": [], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.IDisposable", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "bufferSource", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ICharBufferSource" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "PageSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "Visibility": "Public", - "GenericParameter": [], - "Constant": true, - "Literal": "1024" - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.PartialViewResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FindView", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "viewResult", - "Type": "Microsoft.AspNetCore.Mvc.PartialViewResult" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "view", - "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.IView" - }, - { - "Name": "viewResult", - "Type": "Microsoft.AspNetCore.Mvc.PartialViewResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, + "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Infrastructure.IActionResultExecutor", "Visibility": "Public", "GenericParameter": [] }, @@ -18927,7 +17635,7 @@ }, { "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" + "Type": "Microsoft.AspNetCore.Mvc.Infrastructure.IHttpResponseStreamWriterFactory" }, { "Name": "viewEngine", @@ -18956,1625 +17664,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.SaveTempDataFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Microsoft.AspNetCore.Mvc.Filters.IResultFilter" - ], - "Members": [ - { - "Kind": "Method", - "Name": "OnResourceExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResourceExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResourceExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResourceFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResultExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutingContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnResultExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.ResultExecutedContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IResultFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.SaveTempDataPropertyFilterBase", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ISaveTempDataCallback" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Properties", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IList", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Properties", - "Parameters": [ - { - "Name": "value", - "Type": "System.Collections.Generic.IList" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Subject", - "Parameters": [], - "ReturnType": "System.Object", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Subject", - "Parameters": [ - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_OriginalValues", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnTempDataSaving", - "Parameters": [ - { - "Name": "tempData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ISaveTempDataCallback", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetTempDataProperties", - "Parameters": [ - { - "Name": "type", - "Type": "System.Type" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SetPropertyVaules", - "Parameters": [ - { - "Name": "tempData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionary" - }, - { - "Name": "subject", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "factory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "_factory", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory", - "ReadOnly": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "Prefix", - "Parameters": [], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Protected", - "GenericParameter": [], - "Constant": true, - "Literal": "\"TempDataProperty-\"" - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataApplicationModelProvider", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Order", - "Parameters": [], - "ReturnType": "System.Int32", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuted", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnProvidersExecuting", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ApplicationModels.ApplicationModelProviderContext" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.ApplicationModels.IApplicationModelProvider", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataMvcOptionsSetup", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.Extensions.Options.IConfigureOptions" - ], - "Members": [ - { - "Kind": "Method", - "Name": "Configure", - "Parameters": [ - { - "Name": "options", - "Type": "Microsoft.AspNetCore.Mvc.MvcOptions" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.Extensions.Options.IConfigureOptions", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataProperty", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_TempDataKey", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_PropertyInfo", - "Parameters": [], - "ReturnType": "System.Reflection.PropertyInfo", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetValue", - "Parameters": [ - { - "Name": "obj", - "Type": "System.Object" - } - ], - "ReturnType": "System.Object", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SetValue", - "Parameters": [ - { - "Name": "obj", - "Type": "System.Object" - }, - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "tempDataKey", - "Type": "System.String" - }, - { - "Name": "propertyInfo", - "Type": "System.Reflection.PropertyInfo" - }, - { - "Name": "getter", - "Type": "System.Func" - }, - { - "Name": "setter", - "Type": "System.Action" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TempDataSerializer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Deserialize", - "Parameters": [ - { - "Name": "value", - "Type": "System.Byte[]" - } - ], - "ReturnType": "System.Collections.Generic.IDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Serialize", - "Parameters": [ - { - "Name": "values", - "Type": "System.Collections.Generic.IDictionary" - } - ], - "ReturnType": "System.Byte[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "EnsureObjectCanBeSerialized", - "Parameters": [ - { - "Name": "item", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidateAntiforgeryTokenAuthorizationFilter", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", - "Microsoft.AspNetCore.Mvc.ViewFeatures.IAntiforgeryPolicy" - ], - "Members": [ - { - "Kind": "Method", - "Name": "OnAuthorizationAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Mvc.Filters.IAsyncAuthorizationFilter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ShouldValidate", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.Filters.AuthorizationFilterContext" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "antiforgery", - "Type": "Microsoft.AspNetCore.Antiforgery.IAntiforgery" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ValidationHelpers", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "GetModelErrorMessageOrDefault", - "Parameters": [ - { - "Name": "modelError", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelError" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetModelErrorMessageOrDefault", - "Parameters": [ - { - "Name": "modelError", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelError" - }, - { - "Name": "containingEntry", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateEntry" - }, - { - "Name": "modelExplorer", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer" - } - ], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetModelStateList", - "Parameters": [ - { - "Name": "viewData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" - }, - { - "Name": "excludePropertyErrors", - "Type": "System.Boolean" - } - ], - "ReturnType": "System.Collections.Generic.IList", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBuffer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "Microsoft.AspNetCore.Html.IHtmlContentBuilder" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Item", - "Parameters": [ - { - "Name": "index", - "Type": "System.Int32" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferPage", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Append", - "Parameters": [ - { - "Name": "unencoded", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AppendHtml", - "Parameters": [ - { - "Name": "content", - "Type": "Microsoft.AspNetCore.Html.IHtmlContent" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "AppendHtml", - "Parameters": [ - { - "Name": "encoded", - "Type": "System.String" - } - ], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Clear", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentBuilder", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteTo", - "Parameters": [ - { - "Name": "writer", - "Type": "System.IO.TextWriter" - }, - { - "Name": "encoder", - "Type": "System.Text.Encodings.Web.HtmlEncoder" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContent", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteToAsync", - "Parameters": [ - { - "Name": "writer", - "Type": "System.IO.TextWriter" - }, - { - "Name": "encoder", - "Type": "System.Text.Encodings.Web.HtmlEncoder" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CopyTo", - "Parameters": [ - { - "Name": "destination", - "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentContainer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "MoveTo", - "Parameters": [ - { - "Name": "destination", - "Type": "Microsoft.AspNetCore.Html.IHtmlContentBuilder" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "Microsoft.AspNetCore.Html.IHtmlContentContainer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "bufferScope", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope" - }, - { - "Name": "name", - "Type": "System.String" - }, - { - "Name": "pageSize", - "Type": "System.Int32" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "PartialViewPageSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "TagHelperPageSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "ViewComponentPageSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "ViewPageSize", - "Parameters": [], - "ReturnType": "System.Int32", - "Static": true, - "ReadOnly": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferPage", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Buffer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Capacity", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Count", - "Parameters": [], - "ReturnType": "System.Int32", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Count", - "Parameters": [ - { - "Name": "value", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsFull", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Append", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "buffer", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue[]" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferTextWriter", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "System.IO.TextWriter", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Encoding", - "Parameters": [], - "ReturnType": "System.Text.Encoding", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IsBuffering", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Buffer", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBuffer", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - }, - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Html.IHtmlContent" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Write", - "Parameters": [ - { - "Name": "value", - "Type": "Microsoft.AspNetCore.Html.IHtmlContentContainer" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLine", - "Parameters": [ - { - "Name": "value", - "Type": "System.Object" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "buffer", - "Type": "System.Char[]" - }, - { - "Name": "index", - "Type": "System.Int32" - }, - { - "Name": "count", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLine", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLine", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLineAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLineAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.Char[]" - }, - { - "Name": "start", - "Type": "System.Int32" - }, - { - "Name": "offset", - "Type": "System.Int32" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLineAsync", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "WriteLineAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Flush", - "Parameters": [], - "ReturnType": "System.Void", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FlushAsync", - "Parameters": [], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Override": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "buffer", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBuffer" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "buffer", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBuffer" - }, - { - "Name": "encoding", - "Type": "System.Text.Encoding" - }, - { - "Name": "htmlEncoder", - "Type": "System.Text.Encodings.Web.HtmlEncoder" - }, - { - "Name": "inner", - "Type": "System.IO.TextWriter" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewBufferValue", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Value", - "Parameters": [], - "ReturnType": "System.Object", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "value", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "content", - "Type": "Microsoft.AspNetCore.Html.IHtmlContent" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewComponentResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "context", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "viewComponentResult", - "Type": "Microsoft.AspNetCore.Mvc.ViewComponentResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "mvcHelperOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "htmlEncoder", - "Type": "System.Text.Encodings.Web.HtmlEncoder" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - }, - { - "Name": "tempDataDictionaryFactory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewDataDictionaryFactory", - "Visibility": "Public", - "Kind": "Class", - "Abstract": true, - "Static": true, - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "CreateFactory", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Reflection.TypeInfo" - } - ], - "ReturnType": "System.Func", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "CreateNestedFactory", - "Parameters": [ - { - "Name": "modelType", - "Type": "System.Reflection.TypeInfo" - } - ], - "ReturnType": "System.Func", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewResultExecutor", - "Visibility": "Public", - "Kind": "Class", - "BaseType": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Logger", - "Parameters": [], - "ReturnType": "Microsoft.Extensions.Logging.ILogger", - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "FindView", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "viewResult", - "Type": "Microsoft.AspNetCore.Mvc.ViewResult" - } - ], - "ReturnType": "Microsoft.AspNetCore.Mvc.ViewEngines.ViewEngineResult", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ExecuteAsync", - "Parameters": [ - { - "Name": "actionContext", - "Type": "Microsoft.AspNetCore.Mvc.ActionContext" - }, - { - "Name": "view", - "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.IView" - }, - { - "Name": "viewResult", - "Type": "Microsoft.AspNetCore.Mvc.ViewResult" - } - ], - "ReturnType": "System.Threading.Tasks.Task", - "Virtual": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewOptions", - "Type": "Microsoft.Extensions.Options.IOptions" - }, - { - "Name": "writerFactory", - "Type": "Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory" - }, - { - "Name": "viewEngine", - "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine" - }, - { - "Name": "tempDataFactory", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory" - }, - { - "Name": "diagnosticSource", - "Type": "System.Diagnostics.DiagnosticSource" - }, - { - "Name": "loggerFactory", - "Type": "Microsoft.Extensions.Logging.ILoggerFactory" - }, - { - "Name": "modelMetadataProvider", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.ViewComponentInvokerCache", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "collectionProvider", - "Type": "Microsoft.AspNetCore.Mvc.ViewComponents.IViewComponentDescriptorCollectionProvider" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TemplateBuilder", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Build", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewEngine", - "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine" - }, - { - "Name": "bufferScope", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope" - }, - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - }, - { - "Name": "viewData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" - }, - { - "Name": "modelExplorer", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ModelExplorer" - }, - { - "Name": "htmlFieldName", - "Type": "System.String" - }, - { - "Name": "templateName", - "Type": "System.String" - }, - { - "Name": "readOnly", - "Type": "System.Boolean" - }, - { - "Name": "additionalViewData", - "Type": "System.Object" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.TemplateRenderer", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Render", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Html.IHtmlContent", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetTypeNames", - "Parameters": [ - { - "Name": "modelMetadata", - "Type": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelMetadata" - }, - { - "Name": "fieldType", - "Type": "System.Type" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Static": true, - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "viewEngine", - "Type": "Microsoft.AspNetCore.Mvc.ViewEngines.IViewEngine" - }, - { - "Name": "bufferScope", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.Internal.IViewBufferScope" - }, - { - "Name": "viewContext", - "Type": "Microsoft.AspNetCore.Mvc.Rendering.ViewContext" - }, - { - "Name": "viewData", - "Type": "Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary" - }, - { - "Name": "templateName", - "Type": "System.String" - }, - { - "Name": "readOnly", - "Type": "System.Boolean" - } - ], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Field", - "Name": "IEnumerableOfIFormFileName", - "Parameters": [], - "ReturnType": "System.String", - "Static": true, - "Visibility": "Public", - "GenericParameter": [], - "Constant": true, - "Literal": "\"IEnumerable`IFormFile\"" - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.Extensions.DependencyInjection.MvcViewFeaturesMvcBuilderExtensions", "Visibility": "Public", @@ -20778,17 +17867,6 @@ "System.Collections.Generic.IEnumerator>" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "System.Collections.Generic.KeyValuePair", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator>", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -20822,6 +17900,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "System.Collections.Generic.KeyValuePair", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator>", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -20837,94 +17926,6 @@ ], "GenericParameters": [] }, - { - "Name": "Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable+Awaiter", - "Visibility": "Public", - "Kind": "Struct", - "Sealed": true, - "ImplementedInterfaces": [ - "System.Runtime.CompilerServices.ICriticalNotifyCompletion" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_IsCompleted", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "GetResult", - "Parameters": [], - "ReturnType": "System.Object", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "OnCompleted", - "Parameters": [ - { - "Name": "continuation", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Runtime.CompilerServices.INotifyCompletion", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "UnsafeOnCompleted", - "Parameters": [ - { - "Name": "continuation", - "Type": "System.Action" - } - ], - "ReturnType": "System.Void", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Runtime.CompilerServices.ICriticalNotifyCompletion", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "customAwaiter", - "Type": "System.Object" - }, - { - "Name": "isCompletedMethod", - "Type": "System.Func" - }, - { - "Name": "getResultMethod", - "Type": "System.Func" - }, - { - "Name": "onCompletedMethod", - "Type": "System.Action" - }, - { - "Name": "unsafeOnCompletedMethod", - "Type": "System.Action" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, { "Name": "Microsoft.AspNetCore.Mvc.ViewFeatures.AttributeDictionary+KeyCollection+Enumerator", "Visibility": "Public", @@ -20934,17 +17935,6 @@ "System.Collections.Generic.IEnumerator" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -20978,6 +17968,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -21002,17 +18003,6 @@ "System.Collections.Generic.IEnumerator" ], "Members": [ - { - "Kind": "Method", - "Name": "get_Current", - "Parameters": [], - "ReturnType": "System.String", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerator", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "Dispose", @@ -21046,6 +18036,17 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Current", + "Parameters": [], + "ReturnType": "System.String", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerator", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/breakingchanges.netcore.json deleted file mode 100644 index 1388063181..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/breakingchanges.netcore.json +++ /dev/null @@ -1,17 +0,0 @@ -[ - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "MemberId": "protected .ctor(Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory writerFactory, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine viewEngine, System.Diagnostics.DiagnosticSource diagnosticSource)", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "MemberId": "protected Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory get_WriterFactory()", - "Kind": "Removal" - }, - { - "TypeId": "public class Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor", - "MemberId": "public .ctor(Microsoft.Extensions.Options.IOptions viewOptions, Microsoft.AspNetCore.Mvc.Internal.IHttpResponseStreamWriterFactory writerFactory, Microsoft.AspNetCore.Mvc.ViewEngines.ICompositeViewEngine viewEngine, Microsoft.AspNetCore.Mvc.ViewFeatures.ITempDataDictionaryFactory tempDataFactory, System.Diagnostics.DiagnosticSource diagnosticSource, Microsoft.AspNetCore.Mvc.ModelBinding.IModelMetadataProvider modelMetadataProvider)", - "Kind": "Removal" - } -] diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json index 6e5fbd84f8..cd830febca 100644 --- a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.WebApiCompatShim, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc.WebApiCompatShim, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "System.Net.Http.HttpRequestMessageExtensions", @@ -392,483 +392,6 @@ ], "GenericParameters": [] }, - { - "Name": "System.Net.Http.Formatting.ContentNegotiationResult", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Formatter", - "Parameters": [], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_Formatter", - "Parameters": [ - { - "Name": "value", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_MediaType", - "Parameters": [], - "ReturnType": "System.Net.Http.Headers.MediaTypeHeaderValue", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "set_MediaType", - "Parameters": [ - { - "Name": "value", - "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" - } - ], - "ReturnType": "System.Void", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - }, - { - "Name": "mediaType", - "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "System.Net.Http.Formatting.DefaultContentNegotiator", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "System.Net.Http.Formatting.IContentNegotiator" - ], - "Members": [ - { - "Kind": "Method", - "Name": "get_ExcludeMatchOnTypeOnly", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "Negotiate", - "Parameters": [ - { - "Name": "type", - "Type": "System.Type" - }, - { - "Name": "request", - "Type": "System.Net.Http.HttpRequestMessage" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "System.Net.Http.Formatting.ContentNegotiationResult", - "Virtual": true, - "ImplementedInterface": "System.Net.Http.Formatting.IContentNegotiator", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ComputeFormatterMatches", - "Parameters": [ - { - "Name": "type", - "Type": "System.Type" - }, - { - "Name": "request", - "Type": "System.Net.Http.HttpRequestMessage" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "System.Collections.ObjectModel.Collection", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectResponseMediaTypeFormatter", - "Parameters": [ - { - "Name": "matches", - "Type": "System.Collections.Generic.ICollection" - } - ], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SelectResponseCharacterEncoding", - "Parameters": [ - { - "Name": "request", - "Type": "System.Net.Http.HttpRequestMessage" - }, - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - } - ], - "ReturnType": "System.Text.Encoding", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "MatchAcceptHeader", - "Parameters": [ - { - "Name": "sortedAcceptValues", - "Type": "System.Collections.Generic.IEnumerable" - }, - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - } - ], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "MatchRequestMediaType", - "Parameters": [ - { - "Name": "request", - "Type": "System.Net.Http.HttpRequestMessage" - }, - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - } - ], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "ShouldMatchOnType", - "Parameters": [ - { - "Name": "sortedAcceptValues", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "System.Boolean", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "MatchType", - "Parameters": [ - { - "Name": "type", - "Type": "System.Type" - }, - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - } - ], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SortMediaTypeWithQualityHeaderValuesByQFactor", - "Parameters": [ - { - "Name": "headerValues", - "Type": "System.Collections.Generic.ICollection" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "SortStringWithQualityHeaderValuesByQFactor", - "Parameters": [ - { - "Name": "headerValues", - "Type": "System.Collections.Generic.ICollection" - } - ], - "ReturnType": "System.Collections.Generic.IEnumerable", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "UpdateBestMatch", - "Parameters": [ - { - "Name": "current", - "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatch" - }, - { - "Name": "potentialReplacement", - "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatch" - } - ], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Virtual": true, - "Visibility": "Protected", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [], - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "excludeMatchOnTypeOnly", - "Type": "System.Boolean" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "System.Net.Http.Formatting.FormDataCollection", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [ - "System.Collections.Generic.IEnumerable>" - ], - "Members": [ - { - "Kind": "Method", - "Name": "GetEnumerator", - "Parameters": [], - "ReturnType": "System.Collections.Generic.IEnumerator>", - "Sealed": true, - "Virtual": true, - "ImplementedInterface": "System.Collections.Generic.IEnumerable>", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "query", - "Type": "System.String" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "System.Net.Http.Formatting.IContentNegotiator", - "Visibility": "Public", - "Kind": "Interface", - "Abstract": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "Negotiate", - "Parameters": [ - { - "Name": "type", - "Type": "System.Type" - }, - { - "Name": "request", - "Type": "System.Net.Http.HttpRequestMessage" - }, - { - "Name": "formatters", - "Type": "System.Collections.Generic.IEnumerable" - } - ], - "ReturnType": "System.Net.Http.Formatting.ContentNegotiationResult", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "System.Net.Http.Formatting.MediaTypeFormatterMatch", - "Visibility": "Public", - "Kind": "Class", - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Method", - "Name": "get_Formatter", - "Parameters": [], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatter", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_MediaType", - "Parameters": [], - "ReturnType": "System.Net.Http.Headers.MediaTypeHeaderValue", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Quality", - "Parameters": [], - "ReturnType": "System.Double", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_Ranking", - "Parameters": [], - "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Constructor", - "Name": ".ctor", - "Parameters": [ - { - "Name": "formatter", - "Type": "System.Net.Http.Formatting.MediaTypeFormatter" - }, - { - "Name": "mediaType", - "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" - }, - { - "Name": "quality", - "Type": "System.Nullable" - }, - { - "Name": "ranking", - "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking" - } - ], - "Visibility": "Public", - "GenericParameter": [] - } - ], - "GenericParameters": [] - }, - { - "Name": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking", - "Visibility": "Public", - "Kind": "Enumeration", - "Sealed": true, - "ImplementedInterfaces": [], - "Members": [ - { - "Kind": "Field", - "Name": "None", - "Parameters": [], - "GenericParameter": [], - "Literal": "0" - }, - { - "Kind": "Field", - "Name": "MatchOnCanWriteType", - "Parameters": [], - "GenericParameter": [], - "Literal": "1" - }, - { - "Kind": "Field", - "Name": "MatchOnRequestAcceptHeaderLiteral", - "Parameters": [], - "GenericParameter": [], - "Literal": "2" - }, - { - "Kind": "Field", - "Name": "MatchOnRequestAcceptHeaderSubtypeMediaRange", - "Parameters": [], - "GenericParameter": [], - "Literal": "3" - }, - { - "Kind": "Field", - "Name": "MatchOnRequestAcceptHeaderAllMediaRange", - "Parameters": [], - "GenericParameter": [], - "Literal": "4" - }, - { - "Kind": "Field", - "Name": "MatchOnRequestMediaType", - "Parameters": [], - "GenericParameter": [], - "Literal": "5" - } - ], - "GenericParameters": [] - }, { "Name": "System.Web.Http.ApiController", "Visibility": "Public", @@ -1434,14 +957,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_Message", - "Parameters": [], - "ReturnType": "System.String", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ExecuteResultAsync", @@ -1458,6 +973,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Message", + "Parameters": [], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1497,22 +1020,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_Exception", - "Parameters": [], - "ReturnType": "System.Exception", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IncludeErrorDetail", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ExecuteResultAsync", @@ -1529,6 +1036,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Exception", + "Parameters": [], + "ReturnType": "System.Exception", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IncludeErrorDetail", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1920,22 +1443,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_ModelState", - "Parameters": [], - "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", - "Visibility": "Public", - "GenericParameter": [] - }, - { - "Kind": "Method", - "Name": "get_IncludeErrorDetail", - "Parameters": [], - "ReturnType": "System.Boolean", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ExecuteResultAsync", @@ -1952,6 +1459,22 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_ModelState", + "Parameters": [], + "ReturnType": "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_IncludeErrorDetail", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -1978,14 +1501,6 @@ "BaseType": "Microsoft.AspNetCore.Mvc.ObjectResult", "ImplementedInterfaces": [], "Members": [ - { - "Kind": "Method", - "Name": "get_Content", - "Parameters": [], - "ReturnType": "T0", - "Visibility": "Public", - "GenericParameter": [] - }, { "Kind": "Method", "Name": "ExecuteResultAsync", @@ -2002,6 +1517,14 @@ "Visibility": "Public", "GenericParameter": [] }, + { + "Kind": "Method", + "Name": "get_Content", + "Parameters": [], + "ReturnType": "T0", + "Visibility": "Public", + "GenericParameter": [] + }, { "Kind": "Constructor", "Name": ".ctor", @@ -3065,6 +2588,579 @@ } ], "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.ContentNegotiationResult", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Formatter", + "Parameters": [], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_Formatter", + "Parameters": [ + { + "Name": "value", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MediaType", + "Parameters": [], + "ReturnType": "System.Net.Http.Headers.MediaTypeHeaderValue", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "set_MediaType", + "Parameters": [ + { + "Name": "value", + "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "ReturnType": "System.Void", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + }, + { + "Name": "mediaType", + "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.DefaultContentNegotiator", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Net.Http.Formatting.IContentNegotiator" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_ExcludeMatchOnTypeOnly", + "Parameters": [], + "ReturnType": "System.Boolean", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Negotiate", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatters", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Net.Http.Formatting.ContentNegotiationResult", + "Virtual": true, + "ImplementedInterface": "System.Net.Http.Formatting.IContentNegotiator", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ComputeFormatterMatches", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatters", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Collections.ObjectModel.Collection", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SelectResponseMediaTypeFormatter", + "Parameters": [ + { + "Name": "matches", + "Type": "System.Collections.Generic.ICollection" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SelectResponseCharacterEncoding", + "Parameters": [ + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Text.Encoding", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MatchMediaTypeMapping", + "Parameters": [ + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MatchAcceptHeader", + "Parameters": [ + { + "Name": "sortedAcceptValues", + "Type": "System.Collections.Generic.IEnumerable" + }, + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MatchRequestMediaType", + "Parameters": [ + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ShouldMatchOnType", + "Parameters": [ + { + "Name": "sortedAcceptValues", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Boolean", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "MatchType", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SortMediaTypeWithQualityHeaderValuesByQFactor", + "Parameters": [ + { + "Name": "headerValues", + "Type": "System.Collections.Generic.ICollection" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "SortStringWithQualityHeaderValuesByQFactor", + "Parameters": [ + { + "Name": "headerValues", + "Type": "System.Collections.Generic.ICollection" + } + ], + "ReturnType": "System.Collections.Generic.IEnumerable", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "UpdateBestMatch", + "Parameters": [ + { + "Name": "current", + "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatch" + }, + { + "Name": "potentialReplacement", + "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatch" + } + ], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Virtual": true, + "Visibility": "Protected", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "excludeMatchOnTypeOnly", + "Type": "System.Boolean" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.FormDataCollection", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [ + "System.Collections.Generic.IEnumerable>" + ], + "Members": [ + { + "Kind": "Method", + "Name": "get_Item", + "Parameters": [ + { + "Name": "name", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "ReadAsNameValueCollection", + "Parameters": [], + "ReturnType": "System.Collections.Specialized.NameValueCollection", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "Get", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.String", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetValues", + "Parameters": [ + { + "Name": "key", + "Type": "System.String" + } + ], + "ReturnType": "System.String[]", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "GetEnumerator", + "Parameters": [], + "ReturnType": "System.Collections.Generic.IEnumerator>", + "Sealed": true, + "Virtual": true, + "ImplementedInterface": "System.Collections.Generic.IEnumerable>", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "pairs", + "Type": "System.Collections.Generic.IEnumerable>" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "uri", + "Type": "System.Uri" + } + ], + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "query", + "Type": "System.String" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.IContentNegotiator", + "Visibility": "Public", + "Kind": "Interface", + "Abstract": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "Negotiate", + "Parameters": [ + { + "Name": "type", + "Type": "System.Type" + }, + { + "Name": "request", + "Type": "System.Net.Http.HttpRequestMessage" + }, + { + "Name": "formatters", + "Type": "System.Collections.Generic.IEnumerable" + } + ], + "ReturnType": "System.Net.Http.Formatting.ContentNegotiationResult", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.MediaTypeFormatterMatch", + "Visibility": "Public", + "Kind": "Class", + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Method", + "Name": "get_Formatter", + "Parameters": [], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatter", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_MediaType", + "Parameters": [], + "ReturnType": "System.Net.Http.Headers.MediaTypeHeaderValue", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Quality", + "Parameters": [], + "ReturnType": "System.Double", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Method", + "Name": "get_Ranking", + "Parameters": [], + "ReturnType": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking", + "Visibility": "Public", + "GenericParameter": [] + }, + { + "Kind": "Constructor", + "Name": ".ctor", + "Parameters": [ + { + "Name": "formatter", + "Type": "System.Net.Http.Formatting.MediaTypeFormatter" + }, + { + "Name": "mediaType", + "Type": "System.Net.Http.Headers.MediaTypeHeaderValue" + }, + { + "Name": "quality", + "Type": "System.Nullable" + }, + { + "Name": "ranking", + "Type": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking" + } + ], + "Visibility": "Public", + "GenericParameter": [] + } + ], + "GenericParameters": [] + }, + { + "Name": "System.Net.Http.Formatting.MediaTypeFormatterMatchRanking", + "Visibility": "Public", + "Kind": "Enumeration", + "Sealed": true, + "ImplementedInterfaces": [], + "Members": [ + { + "Kind": "Field", + "Name": "None", + "Parameters": [], + "GenericParameter": [], + "Literal": "0" + }, + { + "Kind": "Field", + "Name": "MatchOnCanWriteType", + "Parameters": [], + "GenericParameter": [], + "Literal": "1" + }, + { + "Kind": "Field", + "Name": "MatchOnRequestAcceptHeaderLiteral", + "Parameters": [], + "GenericParameter": [], + "Literal": "2" + }, + { + "Kind": "Field", + "Name": "MatchOnRequestAcceptHeaderSubtypeMediaRange", + "Parameters": [], + "GenericParameter": [], + "Literal": "3" + }, + { + "Kind": "Field", + "Name": "MatchOnRequestAcceptHeaderAllMediaRange", + "Parameters": [], + "GenericParameter": [], + "Literal": "4" + }, + { + "Kind": "Field", + "Name": "MatchOnRequestWithMediaTypeMapping", + "Parameters": [], + "GenericParameter": [], + "Literal": "5" + }, + { + "Kind": "Field", + "Name": "MatchOnRequestMediaType", + "Parameters": [], + "GenericParameter": [], + "Literal": "6" + } + ], + "GenericParameters": [] } ] } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/breakingchanges.netcore.json deleted file mode 100644 index 1ceaeb35b2..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/breakingchanges.netcore.json +++ /dev/null @@ -1,7 +0,0 @@ -[ - { - "TypeId": "public enum System.Net.Http.Formatting.MediaTypeFormatterMatchRanking", - "MemberId": "MatchOnRequestMediaType = 5", - "Kind": "Removal" - } -] diff --git a/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json b/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json index c6b3ff95ff..50cfc70d58 100644 --- a/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json +++ b/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json @@ -1,5 +1,5 @@ { - "AssemblyIdentity": "Microsoft.AspNetCore.Mvc, Version=2.0.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", + "AssemblyIdentity": "Microsoft.AspNetCore.Mvc, Version=2.1.1.0, Culture=neutral, PublicKeyToken=adb9793829ddae60", "Types": [ { "Name": "Microsoft.Extensions.DependencyInjection.MvcServiceCollectionExtensions", From 6df28ef09a9d50e4601fc832a787b6d5c6635dc9 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 15 Jun 2018 09:30:07 -0700 Subject: [PATCH 054/316] Reach to Dispatcher changes and fix build break --- .../Internal/MvcEndpointDataSource.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 131e545e67..f91863b862 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -101,7 +101,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal action.RouteValues, action.AttributeRouteInfo.Order, metadataCollection, - action.DisplayName)); + action.DisplayName, + new Address(action.AttributeRouteInfo.Name))); } } From a7bc5d6d40a4c7bc4bb6ef2ba4254c59c3f5c471 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 15 Jun 2018 10:34:11 -0700 Subject: [PATCH 055/316] Use feature branch build of Routing to fix build break --- build/dependencies.props | 150 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3f7994e63f..5016eb5768 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34462 - 2.2.0-preview1-17082 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 + 2.2.0-preview1-34481 + 2.2.0-preview1-17090 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-a-preview1-a-16693 + 2.2.0-a-preview1-a-16693 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34462 + 2.2.0-preview1-34481 1.7.0 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-26612-04 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-26614-02 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 2.0.0 2.1.0 - 2.2.0-preview1-26612-04 - 2.2.0-preview1-34462 - 2.2.0-preview1-34462 + 2.2.0-preview1-26614-02 + 2.2.0-preview1-34481 + 2.2.0-preview1-34481 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26611-02 - 4.6.0-preview1-26611-02 - 4.6.0-preview1-26611-02 + 4.6.0-preview1-26613-07 + 4.6.0-preview1-26613-07 + 4.6.0-preview1-26613-07 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 284486474a..a8109db529 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17082 -commithash:13b85a32c7aa9d62f6f3cc211c5c7c566d16b3dd +version:2.2.0-preview1-17090 +commithash:b19e903e946579cd9482089bce7d917e8bacd765 From bc49a82d029fc09357964abda2d43412189d0150 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 15 Jun 2018 11:12:57 -0700 Subject: [PATCH 056/316] Use feature branch package of Localization --- build/dependencies.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 5016eb5768..5dba7fae00 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -25,8 +25,8 @@ 2.2.0-preview1-34481 2.2.0-preview1-34481 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 + 2.2.0-a-preview1-a-16708 + 2.2.0-a-preview1-a-16708 2.2.0-preview1-34481 2.2.0-preview1-34481 2.2.0-preview1-34481 @@ -62,7 +62,7 @@ 2.2.0-preview1-34481 2.2.0-preview1-34481 2.2.0-preview1-34481 - 2.2.0-preview1-34481 + 2.2.0-a-preview1-a-16708 2.2.0-preview1-34481 2.2.0-preview1-34481 2.2.0-preview1-34481 From 1aea6fd5bd4d41ba68efca3708d26ee02b72a2d1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 15 Jun 2018 09:55:47 -0700 Subject: [PATCH 057/316] Remove instrumentation functional tests Fixes #7921 --- Mvc.sln | 13 ---- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 1 - .../RazorPageExecutionInstrumentationTest.cs | 43 ------------- ...mentationWebSite.Home.ViewWithPartial.html | 20 ------ .../HomeController.cs | 15 ----- .../RazorPageDiagnosticListener.cs | 29 --------- ...PageExecutionInstrumentationWebSite.csproj | 15 ----- .../Startup.cs | 61 ------------------- .../TestRazorCompilationService.cs | 48 --------------- .../Views/Home/ViewWithPartial.cshtml | 5 -- .../Views/Home/_PartialView.cshtml | 4 -- .../Views/_Layout.cshtml | 4 -- .../Views/_ViewStart.cshtml | 5 -- .../readme.md | 4 -- 14 files changed, 267 deletions(-) delete mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageDiagnosticListener.cs delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageExecutionInstrumentationWebSite.csproj delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewWithPartial.cshtml delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/_PartialView.cshtml delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_ViewStart.cshtml delete mode 100644 test/WebSites/RazorPageExecutionInstrumentationWebSite/readme.md diff --git a/Mvc.sln b/Mvc.sln index 830bd9f81e..eef6a57f11 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -51,8 +51,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TagHelpersWebSite", "test\W EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FilesWebSite", "test\WebSites\FilesWebSite\FilesWebSite.csproj", "{0EF9860B-10D7-452F-B0F4-A405B88BEBB3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPageExecutionInstrumentationWebSite", "test\WebSites\RazorPageExecutionInstrumentationWebSite\RazorPageExecutionInstrumentationWebSite.csproj", "{2B2B9876-903C-4065-8D62-2EE832BBA106}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ApplicationModelWebSite", "test\WebSites\ApplicationModelWebSite\ApplicationModelWebSite.csproj", "{CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.WebApiCompatShim", "src\Microsoft.AspNetCore.Mvc.WebApiCompatShim\Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj", "{23D30B8C-04B1-4577-A604-ED27EA1E4A0E}" @@ -334,16 +332,6 @@ Global {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0EF9860B-10D7-452F-B0F4-A405B88BEBB3}.Release|x86.ActiveCfg = Release|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Debug|x86.ActiveCfg = Debug|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Any CPU.Build.0 = Release|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2B2B9876-903C-4065-8D62-2EE832BBA106}.Release|x86.ActiveCfg = Release|Any CPU {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Debug|Any CPU.Build.0 = Debug|Any CPU {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -941,7 +929,6 @@ Global {C6304029-78C8-4604-99BE-2078DCA1DD36} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {6DB9B8D0-80F7-4E70-BBB0-0B4C04D79A47} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {0EF9860B-10D7-452F-B0F4-A405B88BEBB3} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} - {2B2B9876-903C-4065-8D62-2EE832BBA106} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {CAE52CB7-0FAC-4B5B-8251-B0FF837DB657} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {23D30B8C-04B1-4577-A604-ED27EA1E4A0E} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {B2B7BC91-688E-4C1E-A71F-CE948D958DDF} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index a3d5754be1..917d2938cb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -37,7 +37,6 @@ - diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs deleted file mode 100644 index b6e87cf1a5..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageExecutionInstrumentationTest.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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.Net.Http; -using System.Reflection; -using System.Threading.Tasks; -using RazorPageExecutionInstrumentationWebSite; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.FunctionalTests -{ - public class RazorPageExecutionInstrumentationTest : IClassFixture> - { - private static readonly Assembly _resourcesAssembly = - typeof(RazorPageExecutionInstrumentationTest).GetTypeInfo().Assembly; - - public RazorPageExecutionInstrumentationTest(MvcTestFixture fixture) - { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact] - public async Task InstrumentedViews_RenderAsExpected() - { - // Arrange - var outputFile = "compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html"; - var expectedContent = - await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false); - - // Act - var content = await Client.GetStringAsync("http://localhost/Home/ViewWithPartial"); - - // Assert -#if GENERATE_BASELINES - ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, content); -#else - Assert.Equal(expectedContent, content, ignoreLineEndingDifferences: true); -#endif - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html deleted file mode 100644 index 81cbd4fb84..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPageExecutionInstrumentationWebSite.Home.ViewWithPartial.html +++ /dev/null @@ -1,20 +0,0 @@ -/Views/_ViewStart.cshtml: Non-literal at 96 contains 16 characters. -/Views/_ViewStart.cshtml: Literal at 112 contains 2 characters. -/Views/Home/ViewWithPartial.cshtml: Literal at 0 contains 27 characters. -/Views/Home/ViewWithPartial.cshtml: Non-literal at 28 contains 39 characters. -/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters. -/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters. -/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters. -/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters. -/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters. -/Views/Home/ViewWithPartial.cshtml: Literal at 67 contains 2 characters. -/Views/Home/_PartialView.cshtml: Literal at 31 contains 2 characters. -/Views/Home/_PartialView.cshtml: Literal at 33 contains 8 characters. -/Views/Home/_PartialView.cshtml: Non-literal at 41 contains 4 characters. -/Views/Home/_PartialView.cshtml: Literal at 45 contains 1 characters. -/Views/Home/_PartialView.cshtml: Literal at 46 contains 20 characters. -/Views/_Layout.cshtml: Literal at 0 contains 7 characters. -/Views/_Layout.cshtml: Non-literal at 8 contains 12 characters. -/Views/_Layout.cshtml: Literal at 20 contains 2 characters. -/Views/_Layout.cshtml: Non-literal at 23 contains 12 characters. -/Views/_Layout.cshtml: Literal at 35 contains 10 characters. diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs deleted file mode 100644 index 3f23b8f768..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/HomeController.cs +++ /dev/null @@ -1,15 +0,0 @@ -// 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; - -namespace RazorPageExecutionInstrumentationWebSite -{ - public class HomeController : Controller - { - public ActionResult ViewWithPartial() - { - return View(); - } - } -} \ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageDiagnosticListener.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageDiagnosticListener.cs deleted file mode 100644 index 30ca586d01..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageDiagnosticListener.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.Http; -using Microsoft.Extensions.DiagnosticAdapter; - -namespace RazorPageExecutionInstrumentationWebSite -{ - public class RazorPageDiagnosticListener - { - public static readonly object WriterKey = new object(); - - [DiagnosticName("Microsoft.AspNetCore.Mvc.Razor.BeginInstrumentationContext")] - public virtual void OnBeginPageInstrumentationContext( - HttpContext httpContext, - string path, - int position, - int length, - bool isLiteral) - { - var literal = isLiteral ? "Literal" : "Non-literal"; - var text = $"{path}: {literal} at {position} contains {length} characters."; - - var writer = (TextWriter)httpContext.Items[WriterKey]; - writer.WriteLine(text); - } - } -} diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageExecutionInstrumentationWebSite.csproj b/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageExecutionInstrumentationWebSite.csproj deleted file mode 100644 index 8b6090fc0f..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/RazorPageExecutionInstrumentationWebSite.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - $(StandardTestWebsiteTfms) - - - - - - - - - - - diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs deleted file mode 100644 index 76b1886b01..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Startup.cs +++ /dev/null @@ -1,61 +0,0 @@ -// 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.Diagnostics; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Razor.Language; -using Microsoft.Extensions.DependencyInjection; - -namespace RazorPageExecutionInstrumentationWebSite -{ - public class Startup - { - // Set up application services - public void ConfigureServices(IServiceCollection services) - { - // Normalize line endings to avoid changes in instrumentation locations between systems. - services.AddTransient(); - - // Add MVC services to the services container. - services.AddMvc(); - } - - public void Configure(IApplicationBuilder app) - { - var listener = new RazorPageDiagnosticListener(); - var diagnosticSource = app.ApplicationServices.GetRequiredService(); - diagnosticSource.SubscribeWithAdapter(listener); - - app.Use(async (context, next) => - { - using (var writer = new StreamWriter(context.Response.Body)) - { - context.Items[RazorPageDiagnosticListener.WriterKey] = writer; - context.Response.Body = Stream.Null; - await next(); - } - }); - - // Add MVC to the request pipeline - 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(); - } -} - diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs b/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs deleted file mode 100644 index d4171804fb..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/TestRazorCompilationService.cs +++ /dev/null @@ -1,48 +0,0 @@ -// 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 System.Text; -using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Razor.Internal; -using Microsoft.AspNetCore.Razor.Language; - -namespace RazorPageExecutionInstrumentationWebSite -{ - public class TestRazorProjectFileSystem : FileProviderRazorProjectFileSystem - { - public TestRazorProjectFileSystem(IRazorViewEngineFileProviderAccessor fileProviderAccessor, IHostingEnvironment hostingEnvironment) - : base(fileProviderAccessor, hostingEnvironment) - { - } - - public override RazorProjectItem GetItem(string path) - { - var item = (FileProviderRazorProjectItem)base.GetItem(path); - return new TestRazorProjectItem(item); - } - - private class TestRazorProjectItem : FileProviderRazorProjectItem - { - public TestRazorProjectItem(FileProviderRazorProjectItem projectItem) - : base(projectItem.FileInfo, projectItem.BasePath, projectItem.FilePath, projectItem.RelativePhysicalPath) - { - } - - public override Stream Read() - { - // Normalize line endings to '\r\n' (CRLF). This removes core.autocrlf, core.eol, core.safecrlf, and - // .gitattributes from the equation and treats "\r\n" and "\n" as equivalent. Does not handle - // some line endings like "\r" but otherwise ensures checksums and line mappings are consistent. - string text; - using (var streamReader = new StreamReader(base.Read())) - { - text = streamReader.ReadToEnd().Replace("\r", "").Replace("\n", "\r\n"); - } - - var bytes = Encoding.UTF8.GetBytes(text); - return new MemoryStream(bytes); - } - } - } -} \ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewWithPartial.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewWithPartial.cshtml deleted file mode 100644 index 8f42baf024..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/ViewWithPartial.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -view-with-partial-content -@await Html.PartialAsync("_PartialView") -@{ -await Html.RenderPartialAsync("_PartialView"); -} \ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/_PartialView.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/_PartialView.cshtml deleted file mode 100644 index 3b945a4613..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/Home/_PartialView.cshtml +++ /dev/null @@ -1,4 +0,0 @@ -@{ - var cls = "class"; -} -

partial-content

\ No newline at end of file diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml deleted file mode 100644 index 23e469ccc9..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_Layout.cshtml +++ /dev/null @@ -1,4 +0,0 @@ -
-@int.MaxValue -@RenderBody() -
diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_ViewStart.cshtml b/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_ViewStart.cshtml deleted file mode 100644 index 3b3abdacaa..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/Views/_ViewStart.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@{ - Layout = "/Views/_Layout.cshtml"; - var viewStartMessage = "viewstart-content"; -} -@viewStartMessage diff --git a/test/WebSites/RazorPageExecutionInstrumentationWebSite/readme.md b/test/WebSites/RazorPageExecutionInstrumentationWebSite/readme.md deleted file mode 100644 index b5de53569b..0000000000 --- a/test/WebSites/RazorPageExecutionInstrumentationWebSite/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -RazorPageExecutionInstrumentationWebSite -=== - -This web site is used for functional testing of page instrumentation. From a712ccc98ae84d208bd5f0817ed8e6320ca163c6 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 15 Jun 2018 09:45:41 -0700 Subject: [PATCH 058/316] Fix up error message when compilation references are missing --- .../CompilationFailedExceptionFactory.cs | 5 ++-- .../Properties/Resources.Designer.cs | 12 ++++----- .../Resources.resx | 4 +-- .../AntiforgeryTestHelper.cs | 2 +- .../ErrorPageTests.cs | 5 ++-- .../CompilerFailedExceptionFactoryTest.cs | 25 +++++++++++++++++++ 6 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs index 0be6fa6b7d..02b096ec75 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs @@ -77,9 +77,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal string.Equals(CS0234, g.Id, StringComparison.OrdinalIgnoreCase) || string.Equals(CS0246, g.Id, StringComparison.OrdinalIgnoreCase))) { - additionalMessage = Resources.FormatCompilation_DependencyContextIsNotSpecified( - "Microsoft.NET.Sdk.Web", - "PreserveCompilationContext"); + additionalMessage = Resources.FormatCompilation_MissingReferences( + "CopyRefAssembliesToPublishDirectory"); } var compilationFailure = new CompilationFailure( diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs index 3b6a518dd3..a2e6436e78 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs @@ -267,18 +267,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor => string.Format(CultureInfo.CurrentCulture, GetString("LayoutHasCircularReference"), p0, p1); /// - /// One or more compilation references are missing. Ensure that your project is referencing '{0}' and the '{1}' property is not set to false. + /// One or more compilation references may be missing. If you're seeing this in a published application, set '{0}' to true in your project file to ensure files in the refs directory are published. /// - internal static string Compilation_DependencyContextIsNotSpecified + internal static string Compilation_MissingReferences { - get => GetString("Compilation_DependencyContextIsNotSpecified"); + get => GetString("Compilation_MissingReferences"); } /// - /// One or more compilation references are missing. Ensure that your project is referencing '{0}' and the '{1}' property is not set to false. + /// One or more compilation references may be missing. If you're seeing this in a published application, set '{0}' to true in your project file to ensure files in the refs directory are published. /// - internal static string FormatCompilation_DependencyContextIsNotSpecified(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("Compilation_DependencyContextIsNotSpecified"), p0, p1); + internal static string FormatCompilation_MissingReferences(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("Compilation_MissingReferences"), p0); /// /// '{0}' cannot be empty. These locations are required to locate a view for rendering. diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx index 19b0e8f053..bab3fddf83 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx @@ -173,8 +173,8 @@ A circular layout reference was detected when rendering '{0}'. The layout page '{1}' has already been rendered. - - One or more compilation references are missing. Ensure that your project is referencing '{0}' and the '{1}' property is not set to false. + + One or more compilation references may be missing. If you're seeing this in a published application, set '{0}' to true in your project file to ensure files in the refs directory are published. '{0}' cannot be empty. These locations are required to locate a view for rendering. diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs index 17ff0bb735..43bd304d14 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } } - throw new Exception($"Antiforgery token could not be located in {htmlDocument.TextContent}."); + throw new Exception($"Antiforgery token could not be located in {htmlDocument.Source.Text}."); } public static CookieMetadata RetrieveAntiforgeryCookie(HttpResponseMessage response) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs index 644a4ac067..31a74644b1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs @@ -6,7 +6,6 @@ using System.Net.Http; using System.Net.Http.Headers; using System.Text.Encodings.Web; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -17,8 +16,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public class ErrorPageTests : IClassFixture> { private static readonly string PreserveCompilationContextMessage = HtmlEncoder.Default.Encode( - "One or more compilation references are missing. Ensure that your project is referencing " + - "'Microsoft.NET.Sdk.Web' and the 'PreserveCompilationContext' property is not set to false."); + "One or more compilation references may be missing. " + + "If you're seeing this in a published application, set 'CopyRefAssembliesToPublishDirectory' to true in your project file to ensure files in the refs directory are published."); public ErrorPageTests(MvcTestFixture fixture) { Client = fixture.CreateDefaultClient(); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs index 05e156a6ef..394bc2ae7d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; +using System.Linq; using System.Text; using Microsoft.AspNetCore.Mvc.Razor.Extensions; using Microsoft.AspNetCore.Razor.Language; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.Text; using Xunit; @@ -43,6 +45,29 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal message.Message)); } + [Fact] + public void GetCompilationFailedResult_WithMissingReferences() + { + // Arrange + var expected = "One or more compilation references may be missing. If you're seeing this in a published application, set 'CopyRefAssembliesToPublishDirectory' to true in your project file to ensure files in the refs directory are published."; + var compilation = CSharpCompilation.Create("Test", options: new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)); + var syntaxTree = CSharpSyntaxTree.ParseText("@class Test { public string Test { get; set; } }"); + compilation = compilation.AddSyntaxTrees(syntaxTree); + var emitResult = compilation.Emit(new MemoryStream()); + + // Act + var exception = CompilationFailedExceptionFactory.Create( + RazorCodeDocument.Create(RazorSourceDocument.Create("Test", "Index.cshtml"), Enumerable.Empty()), + syntaxTree.ToString(), + "Test", + emitResult.Diagnostics); + + // Assert + Assert.Collection( + exception.CompilationFailures, + failure => Assert.Equal(expected, failure.FailureSummary)); + } + [Fact] public void GetCompilationFailedResult_UsesPhysicalPath() { From e07054e0a7f407b1096412a04d4f315318f8c14e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 15 Jun 2018 12:46:37 -0700 Subject: [PATCH 059/316] Skip failing tests --- .../ViewEngineTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs index 363f534469..eba89d70f7 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs @@ -246,11 +246,11 @@ ViewWithNestedLayout-Content Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/Mvc/issues/7922")] public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByName() => RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByName"); - [Fact] + [Fact(Skip = "https://github.com/aspnet/Mvc/issues/7922")] public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByPath() => RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByPath"); From 4634a97fae258b02e382bccbe7879937dbff8b0e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 18 Jun 2018 09:07:26 -0700 Subject: [PATCH 060/316] Use older shared runtime --- build/dependencies.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/dependencies.props b/build/dependencies.props index 5dba7fae00..39ff053267 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -80,7 +80,7 @@ 2.2.0-preview1-34481 2.0.0 2.1.0 - 2.2.0-preview1-26614-02 + 2.2.0-preview1-26612-04 2.2.0-preview1-34481 2.2.0-preview1-34481 15.6.1 From a0a9c2c58555264491860f06d4e75e215d821738 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 14 Jun 2018 17:46:28 -0700 Subject: [PATCH 061/316] Integrate Dispatcher's link generator Related to https://github.com/aspnet/Routing/issues/530 --- .../Routing/DispatcherUrlHelper.cs | 127 ++ .../Routing/UrlHelper.cs | 288 +-- .../Routing/UrlHelperBase.cs | 293 +++ .../Routing/UrlHelperFactory.cs | 22 +- .../LocalRedirectResultTest.cs | 11 +- .../RedirectResultTest.cs | 12 +- .../Routing/DispatcherUrlHelperTest.cs | 150 ++ .../Routing/UrlHelperBaseTest.cs | 171 ++ .../Routing/UrlHelperExtensionsTest.cs | 585 ++++++ .../Routing/UrlHelperTest.cs | 1815 +---------------- .../Routing/UrlHelperTestBase.cs | 1007 +++++++++ 11 files changed, 2450 insertions(+), 2031 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs new file mode 100644 index 0000000000..bb6e1a1bdc --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + /// + /// An implementation of that uses to build URLs + /// for ASP.NET MVC within an application. + /// + internal class DispatcherUrlHelper : UrlHelperBase + { + private readonly ILogger _logger; + private readonly ILinkGenerator _linkGenerator; + + /// + /// Initializes a new instance of the class using the specified + /// . + /// + /// The for the current request. + /// The used to generate the link. + /// The . + public DispatcherUrlHelper( + ActionContext actionContext, + ILinkGenerator linkGenerator, + ILogger logger) + : base(actionContext) + { + if (linkGenerator == null) + { + throw new ArgumentNullException(nameof(linkGenerator)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + _linkGenerator = linkGenerator; + _logger = logger; + } + + /// + public override string Action(UrlActionContext urlActionContext) + { + if (urlActionContext == null) + { + throw new ArgumentNullException(nameof(urlActionContext)); + } + + var valuesDictionary = GetValuesDictionary(urlActionContext.Values); + + if (urlActionContext.Action == null) + { + if (!valuesDictionary.ContainsKey("action") && + AmbientValues.TryGetValue("action", out var action)) + { + valuesDictionary["action"] = action; + } + } + else + { + valuesDictionary["action"] = urlActionContext.Action; + } + + if (urlActionContext.Controller == null) + { + if (!valuesDictionary.ContainsKey("controller") && + AmbientValues.TryGetValue("controller", out var controller)) + { + valuesDictionary["controller"] = controller; + } + } + else + { + valuesDictionary["controller"] = urlActionContext.Controller; + } + + var successfullyGeneratedLink = _linkGenerator.TryGetLink( + new LinkGeneratorContext() + { + SuppliedValues = valuesDictionary, + AmbientValues = AmbientValues + }, + out var link); + + if (!successfullyGeneratedLink) + { + //TODO: log here + + return null; + } + + return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, link, urlActionContext.Fragment); + } + + /// + public override string RouteUrl(UrlRouteContext routeContext) + { + if (routeContext == null) + { + throw new ArgumentNullException(nameof(routeContext)); + } + + var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); + + var successfullyGeneratedLink = _linkGenerator.TryGetLink( + new LinkGeneratorContext() + { + Address = new Address(routeContext.RouteName), + SuppliedValues = valuesDictionary, + AmbientValues = AmbientValues + }, + out var link); + + if (!successfullyGeneratedLink) + { + return null; + } + + return GenerateUrl(routeContext.Protocol, routeContext.Host, link, routeContext.Fragment); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs index 31a8b4a9fd..8bab2581d4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs @@ -2,9 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; @@ -14,38 +11,18 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// An implementation of that contains methods to /// build URLs for ASP.NET MVC within an application. /// - public class UrlHelper : IUrlHelper + public class UrlHelper : UrlHelperBase { - - // Perf: Share the StringBuilder object across multiple calls of GenerateURL for this UrlHelper - private StringBuilder _stringBuilder; - // Perf: Reuse the RouteValueDictionary across multiple calls of Action for this UrlHelper - private readonly RouteValueDictionary _routeValueDictionary; - /// /// Initializes a new instance of the class using the specified /// . /// /// The for the current request. public UrlHelper(ActionContext actionContext) + : base(actionContext) { - if (actionContext == null) - { - throw new ArgumentNullException(nameof(actionContext)); - } - - ActionContext = actionContext; - _routeValueDictionary = new RouteValueDictionary(); } - /// - public ActionContext ActionContext { get; } - - /// - /// Gets the associated with the current request. - /// - protected RouteValueDictionary AmbientValues => ActionContext.RouteData.Values; - /// /// Gets the associated with the current request. /// @@ -58,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing protected IRouter Router => ActionContext.RouteData.Routers[0]; /// - public virtual string Action(UrlActionContext actionContext) + public override string Action(UrlActionContext actionContext) { if (actionContext == null) { @@ -69,9 +46,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing if (actionContext.Action == null) { - object action; if (!valuesDictionary.ContainsKey("action") && - AmbientValues.TryGetValue("action", out action)) + AmbientValues.TryGetValue("action", out var action)) { valuesDictionary["action"] = action; } @@ -83,9 +59,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing if (actionContext.Controller == null) { - object controller; if (!valuesDictionary.ContainsKey("controller") && - AmbientValues.TryGetValue("controller", out controller)) + AmbientValues.TryGetValue("controller", out var controller)) { valuesDictionary["controller"] = controller; } @@ -100,54 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } /// - public virtual bool IsLocalUrl(string url) - { - if (string.IsNullOrEmpty(url)) - { - return false; - } - - // Allows "/" or "/foo" but not "//" or "/\". - if (url[0] == '/') - { - // url is exactly "/" - if (url.Length == 1) - { - return true; - } - - // url doesn't start with "//" or "/\" - if (url[1] != '/' && url[1] != '\\') - { - return true; - } - - return false; - } - - // Allows "~/" or "~/foo" but not "~//" or "~/\". - if (url[0] == '~' && url.Length > 1 && url[1] == '/') - { - // url is exactly "~/" - if (url.Length == 2) - { - return true; - } - - // url doesn't start with "~//" or "~/\" - if (url[2] != '/' && url[2] != '\\') - { - return true; - } - - return false; - } - - return false; - } - - /// - public virtual string RouteUrl(UrlRouteContext routeContext) + public override string RouteUrl(UrlRouteContext routeContext) { if (routeContext == null) { @@ -167,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// /// The . The uses these values, in combination with - /// , to generate the URL. + /// , to generate the URL. /// /// The . protected virtual VirtualPathData GetVirtualPathData(string routeName, RouteValueDictionary values) @@ -176,128 +104,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing return Router.GetVirtualPath(context); } - // Internal for unit testing. - internal void AppendPathAndFragment(StringBuilder builder, VirtualPathData pathData, string fragment) - { - var pathBase = HttpContext.Request.PathBase; - - if (!pathBase.HasValue) - { - if (pathData.VirtualPath.Length == 0) - { - builder.Append("/"); - } - else - { - if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) - { - builder.Append("/"); - } - - builder.Append(pathData.VirtualPath); - } - } - else - { - if (pathData.VirtualPath.Length == 0) - { - builder.Append(pathBase.Value); - } - else - { - builder.Append(pathBase.Value); - - if (pathBase.Value.EndsWith("/", StringComparison.Ordinal)) - { - builder.Length--; - } - - if (!pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) - { - builder.Append("/"); - } - - builder.Append(pathData.VirtualPath); - } - } - - if (!string.IsNullOrEmpty(fragment)) - { - builder.Append("#").Append(fragment); - } - } - - /// - public virtual string Content(string contentPath) - { - if (string.IsNullOrEmpty(contentPath)) - { - return null; - } - else if (contentPath[0] == '~') - { - var segment = new PathString(contentPath.Substring(1)); - var applicationPath = HttpContext.Request.PathBase; - - return applicationPath.Add(segment).Value; - } - - return contentPath; - } - - /// - public virtual string Link(string routeName, object values) - { - return RouteUrl(new UrlRouteContext() - { - RouteName = routeName, - Values = values, - Protocol = HttpContext.Request.Scheme, - Host = HttpContext.Request.Host.ToUriComponent() - }); - } - - private RouteValueDictionary GetValuesDictionary(object values) - { - // Perf: RouteValueDictionary can be cast to IDictionary, but it is - // special cased to avoid allocating boxed Enumerator. - var routeValuesDictionary = values as RouteValueDictionary; - if (routeValuesDictionary != null) - { - _routeValueDictionary.Clear(); - foreach (var kvp in routeValuesDictionary) - { - _routeValueDictionary.Add(kvp.Key, kvp.Value); - } - - return _routeValueDictionary; - } - - var dictionaryValues = values as IDictionary; - if (dictionaryValues != null) - { - _routeValueDictionary.Clear(); - foreach (var kvp in dictionaryValues) - { - _routeValueDictionary.Add(kvp.Key, kvp.Value); - } - - return _routeValueDictionary; - } - - return new RouteValueDictionary(values); - } - - private StringBuilder GetStringBuilder() - { - if(_stringBuilder == null) - { - _stringBuilder = new StringBuilder(); - } - - return _stringBuilder; - } - /// /// Generates the URL using the specified components. /// @@ -308,85 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The generated URL. protected virtual string GenerateUrl(string protocol, string host, VirtualPathData pathData, string fragment) { - if (pathData == null) - { - return null; - } - - // VirtualPathData.VirtualPath returns string.Empty instead of null. - Debug.Assert(pathData.VirtualPath != null); - - // Perf: In most of the common cases, GenerateUrl is called with a null protocol, host and fragment. - // In such cases, we might not need to build any URL as the url generated is mostly same as the virtual path available in pathData. - // For such common cases, this FastGenerateUrl method saves a string allocation per GenerateUrl call. - string url; - if (TryFastGenerateUrl(protocol, host, pathData, fragment, out url)) - { - return url; - } - - var builder = GetStringBuilder(); - try - { - if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host)) - { - AppendPathAndFragment(builder, pathData, fragment); - // We're returning a partial URL (just path + query + fragment), but we still want it to be rooted. - if (builder.Length == 0 || builder[0] != '/') - { - builder.Insert(0, '/'); - } - } - else - { - protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol; - builder.Append(protocol); - - builder.Append("://"); - - host = string.IsNullOrEmpty(host) ? HttpContext.Request.Host.Value : host; - builder.Append(host); - AppendPathAndFragment(builder, pathData, fragment); - } - - var path = builder.ToString(); - return path; - } - finally - { - // Clear the StringBuilder so that it can reused for the next call. - builder.Clear(); - } - } - - private bool TryFastGenerateUrl( - string protocol, - string host, - VirtualPathData pathData, - string fragment, - out string url) - { - var pathBase = HttpContext.Request.PathBase; - url = null; - - if (string.IsNullOrEmpty(protocol) - && string.IsNullOrEmpty(host) - && string.IsNullOrEmpty(fragment) - && !pathBase.HasValue) - { - if (pathData.VirtualPath.Length == 0) - { - url = "/"; - return true; - } - else if (pathData.VirtualPath.StartsWith("/", StringComparison.Ordinal)) - { - url = pathData.VirtualPath; - return true; - } - } - - return false; + return GenerateUrl(protocol, host, pathData?.VirtualPath, fragment); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs new file mode 100644 index 0000000000..730323f33b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs @@ -0,0 +1,293 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public abstract class UrlHelperBase : IUrlHelper + { + // Perf: Share the StringBuilder object across multiple calls of GenerateURL for this UrlHelper + private StringBuilder _stringBuilder; + + // Perf: Reuse the RouteValueDictionary across multiple calls of Action for this UrlHelper + private readonly RouteValueDictionary _routeValueDictionary; + + protected UrlHelperBase(ActionContext actionContext) + { + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + ActionContext = actionContext; + AmbientValues = actionContext.RouteData.Values; + _routeValueDictionary = new RouteValueDictionary(); + } + + /// + /// Gets the associated with the current request. + /// + protected RouteValueDictionary AmbientValues { get; } + + /// + public ActionContext ActionContext { get; } + + /// + public virtual bool IsLocalUrl(string url) + { + if (string.IsNullOrEmpty(url)) + { + return false; + } + + // Allows "/" or "/foo" but not "//" or "/\". + if (url[0] == '/') + { + // url is exactly "/" + if (url.Length == 1) + { + return true; + } + + // url doesn't start with "//" or "/\" + if (url[1] != '/' && url[1] != '\\') + { + return true; + } + + return false; + } + + // Allows "~/" or "~/foo" but not "~//" or "~/\". + if (url[0] == '~' && url.Length > 1 && url[1] == '/') + { + // url is exactly "~/" + if (url.Length == 2) + { + return true; + } + + // url doesn't start with "~//" or "~/\" + if (url[2] != '/' && url[2] != '\\') + { + return true; + } + + return false; + } + + return false; + } + + /// + public virtual string Content(string contentPath) + { + if (string.IsNullOrEmpty(contentPath)) + { + return null; + } + else if (contentPath[0] == '~') + { + var segment = new PathString(contentPath.Substring(1)); + var applicationPath = ActionContext.HttpContext.Request.PathBase; + + return applicationPath.Add(segment).Value; + } + + return contentPath; + } + + /// + public virtual string Link(string routeName, object values) + { + return RouteUrl(new UrlRouteContext() + { + RouteName = routeName, + Values = values, + Protocol = ActionContext.HttpContext.Request.Scheme, + Host = ActionContext.HttpContext.Request.Host.ToUriComponent() + }); + } + + /// + public abstract string Action(UrlActionContext actionContext); + + /// + public abstract string RouteUrl(UrlRouteContext routeContext); + + protected RouteValueDictionary GetValuesDictionary(object values) + { + // Perf: RouteValueDictionary can be cast to IDictionary, but it is + // special cased to avoid allocating boxed Enumerator. + if (values is RouteValueDictionary routeValuesDictionary) + { + _routeValueDictionary.Clear(); + foreach (var kvp in routeValuesDictionary) + { + _routeValueDictionary.Add(kvp.Key, kvp.Value); + } + + return _routeValueDictionary; + } + + if (values is IDictionary dictionaryValues) + { + _routeValueDictionary.Clear(); + foreach (var kvp in dictionaryValues) + { + _routeValueDictionary.Add(kvp.Key, kvp.Value); + } + + return _routeValueDictionary; + } + + return new RouteValueDictionary(values); + } + + protected string GenerateUrl(string protocol, string host, string virtualPath, string fragment) + { + if (virtualPath == null) + { + return null; + } + + // Perf: In most of the common cases, GenerateUrl is called with a null protocol, host and fragment. + // In such cases, we might not need to build any URL as the url generated is mostly same as the virtual path available in pathData. + // For such common cases, this FastGenerateUrl method saves a string allocation per GenerateUrl call. + string url; + if (TryFastGenerateUrl(protocol, host, virtualPath, fragment, out url)) + { + return url; + } + + var builder = GetStringBuilder(); + try + { + var pathBase = ActionContext.HttpContext.Request.PathBase; + + if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host)) + { + AppendPathAndFragment(builder, pathBase, virtualPath, fragment); + // We're returning a partial URL (just path + query + fragment), but we still want it to be rooted. + if (builder.Length == 0 || builder[0] != '/') + { + builder.Insert(0, '/'); + } + } + else + { + protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol; + builder.Append(protocol); + + builder.Append("://"); + + host = string.IsNullOrEmpty(host) ? ActionContext.HttpContext.Request.Host.Value : host; + builder.Append(host); + AppendPathAndFragment(builder, pathBase, virtualPath, fragment); + } + + var path = builder.ToString(); + return path; + } + finally + { + // Clear the StringBuilder so that it can reused for the next call. + builder.Clear(); + } + } + + // for unit testing + internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment) + { + if (!pathBase.HasValue) + { + if (virtualPath.Length == 0) + { + builder.Append("/"); + } + else + { + if (!virtualPath.StartsWith("/", StringComparison.Ordinal)) + { + builder.Append("/"); + } + + builder.Append(virtualPath); + } + } + else + { + if (virtualPath.Length == 0) + { + builder.Append(pathBase.Value); + } + else + { + builder.Append(pathBase.Value); + + if (pathBase.Value.EndsWith("/", StringComparison.Ordinal)) + { + builder.Length--; + } + + if (!virtualPath.StartsWith("/", StringComparison.Ordinal)) + { + builder.Append("/"); + } + + builder.Append(virtualPath); + } + } + + if (!string.IsNullOrEmpty(fragment)) + { + builder.Append("#").Append(fragment); + } + } + + private bool TryFastGenerateUrl( + string protocol, + string host, + string virtualPath, + string fragment, + out string url) + { + var pathBase = ActionContext.HttpContext.Request.PathBase; + url = null; + + if (string.IsNullOrEmpty(protocol) + && string.IsNullOrEmpty(host) + && string.IsNullOrEmpty(fragment) + && !pathBase.HasValue) + { + if (virtualPath.Length == 0) + { + url = "/"; + return true; + } + else if (virtualPath.StartsWith("/", StringComparison.Ordinal)) + { + url = virtualPath; + return true; + } + } + + return false; + } + + private StringBuilder GetStringBuilder() + { + if (_stringBuilder == null) + { + _stringBuilder = new StringBuilder(); + } + + return _stringBuilder; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index f9b8006850..ba0f7e9607 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -4,6 +4,9 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing { @@ -37,16 +40,27 @@ namespace Microsoft.AspNetCore.Mvc.Routing } // Perf: Create only one UrlHelper per context - object value; - if (httpContext.Items.TryGetValue(typeof(IUrlHelper), out value) && value is IUrlHelper) + if (httpContext.Items.TryGetValue(typeof(IUrlHelper), out var value) && value is IUrlHelper) { return (IUrlHelper)value; } - var urlHelper = new UrlHelper(context); + IUrlHelper urlHelper; + var endpointFeature = httpContext.Features.Get(); + if (endpointFeature?.Endpoint != null) + { + var linkGenerator = httpContext.RequestServices.GetRequiredService(); + var logger = httpContext.RequestServices.GetRequiredService>(); + urlHelper = new DispatcherUrlHelper(context, linkGenerator, logger); + } + else + { + urlHelper = new UrlHelper(context); + } + httpContext.Items[typeof(IUrlHelper)] = urlHelper; return urlHelper; } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs index ebbbac82a7..6e78d6f840 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -168,13 +169,15 @@ namespace Microsoft.AspNetCore.Mvc var serviceProvider = GetServiceProvider(); httpContext.Setup(o => o.Response) - .Returns(response); + .Returns(response); httpContext.SetupGet(o => o.RequestServices) - .Returns(serviceProvider); + .Returns(serviceProvider); httpContext.SetupGet(o => o.Items) - .Returns(new ItemsDictionary()); + .Returns(new ItemsDictionary()); httpContext.Setup(o => o.Request.PathBase) - .Returns(new PathString(appRoot)); + .Returns(new PathString(appRoot)); + httpContext.SetupGet(h => h.Features) + .Returns(new FeatureCollection()); return httpContext.Object; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs index dc37c52ae7..61d583b998 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs @@ -4,10 +4,10 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Internal; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -146,13 +146,15 @@ namespace Microsoft.AspNetCore.Mvc var serviceProvider = GetServiceProvider(); httpContext.Setup(o => o.Response) - .Returns(response); + .Returns(response); httpContext.SetupGet(o => o.RequestServices) - .Returns(serviceProvider); + .Returns(serviceProvider); httpContext.SetupGet(o => o.Items) - .Returns(new ItemsDictionary()); + .Returns(new ItemsDictionary()); httpContext.Setup(o => o.Request.PathBase) - .Returns(new PathString(appRoot)); + .Returns(new PathString(appRoot)); + httpContext.SetupGet(h => h.Features) + .Returns(new FeatureCollection()); return httpContext.Object; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs new file mode 100644 index 0000000000..6c42555191 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs @@ -0,0 +1,150 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public class DispatcherUrlHelperTest : UrlHelperTestBase + { + protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) + { + return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); + } + + protected override IUrlHelper CreateUrlHelperWithDefaultRoutes(string appRoot, string host, string protocol) + { + return CreateUrlHelper(GetDefaultEndpoints(), appRoot, host, protocol); + } + + protected override IUrlHelper CreateUrlHelperWithDefaultRoutes( + string appRoot, + string host, + string protocol, + string routeName, + string template) + { + var endpoints = GetDefaultEndpoints(); + endpoints.Add(new MatcherEndpoint( + next => httpContext => Task.CompletedTask, + template, + null, + 0, + EndpointMetadataCollection.Empty, + null, + new Address(routeName) + )); + return CreateUrlHelper(endpoints, appRoot, host, protocol); + } + + protected override IUrlHelper CreateUrlHelper(ActionContext actionContext) + { + var httpContext = actionContext.HttpContext; + httpContext.Features.Set(new EndpointFeature() + { + Endpoint = new MatcherEndpoint( + next => cntxt => Task.CompletedTask, + "/", + new { }, + 0, + EndpointMetadataCollection.Empty, + null, + null) + }); + + var urlHelperFactory = httpContext.RequestServices.GetRequiredService(); + var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); + Assert.IsType(urlHelper); + return urlHelper; + } + + protected override IServiceProvider CreateServices() + { + return CreateServices(Enumerable.Empty()); + } + + protected override IUrlHelper CreateUrlHelper( + string appRoot, + string host, + string protocol, + string routeName, + string template, + object defaults) + { + var endpoint = GetEndpoint(routeName, template, defaults); + var services = CreateServices(new[] { endpoint }); + var httpContext = CreateHttpContext(services, appRoot: "", host: null, protocol: null); + var actionContext = CreateActionContext(httpContext); + return CreateUrlHelper(actionContext); + } + + private IUrlHelper CreateUrlHelper( + IEnumerable endpoints, + string appRoot, + string host, + string protocol) + { + var serviceProvider = CreateServices(endpoints); + var httpContext = CreateHttpContext(serviceProvider, appRoot, host, protocol); + var actionContext = CreateActionContext(httpContext); + return CreateUrlHelper(actionContext); + } + + private List GetDefaultEndpoints() + { + var endpoints = new List(); + endpoints.Add(new MatcherEndpoint( + next => (httpContext) => Task.CompletedTask, + "{controller}/{action}/{id}", + new { id = "defaultid" }, + 0, + EndpointMetadataCollection.Empty, + "RouteWithNoName", + address: null)); + endpoints.Add(new MatcherEndpoint( + next => (httpContext) => Task.CompletedTask, + "named/{controller}/{action}/{id}", + new { id = "defaultid" }, + 0, + EndpointMetadataCollection.Empty, + "RouteWithNoName", + new Address("namedroute"))); + return endpoints; + } + + private IServiceProvider CreateServices(IEnumerable endpoints) + { + if (endpoints == null) + { + endpoints = Enumerable.Empty(); + } + + var services = GetCommonServices(); + services.AddDispatcher(); + services.TryAddEnumerable( + ServiceDescriptor.Singleton(new DefaultEndpointDataSource(endpoints))); + services.TryAddSingleton(); + return services.BuildServiceProvider(); + } + + private MatcherEndpoint GetEndpoint(string name, string template, object defaults) + { + return new MatcherEndpoint( + next => c => Task.CompletedTask, + template, + defaults, + 0, + EndpointMetadataCollection.Empty, + null, + new Address(name)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs new file mode 100644 index 0000000000..9bb3f54043 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs @@ -0,0 +1,171 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.ObjectPool; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public class UrlHelperBaseTest + { + public static TheoryData GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData => + new TheoryData + { + { null, "", "/" }, + { null, "/", "/" }, + { null, "Hello", "/Hello" }, + { null, "/Hello", "/Hello" }, + { "/", "", "/" }, + { "/", "hello", "/hello" }, + { "/", "/hello", "/hello" }, + { "/hello", "", "/hello" }, + { "/hello/", "", "/hello/" }, + { "/hello", "/", "/hello/" }, + { "/hello/", "world", "/hello/world" }, + { "/hello/", "/world", "/hello/world" }, + { "/hello/", "/world 123", "/hello/world 123" }, + { "/hello/", "/world%20123", "/hello/world%20123" }, + }; + + [Theory] + [MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))] + public void AppendPathAndFragment_HandlesLeadingAndTrailingSlashes( + string appBase, + string virtualPath, + string expected) + { + // Arrange + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appBase, host: null, protocol: null); + var builder = new StringBuilder(); + + // Act + UrlHelperBase.AppendPathAndFragment(builder, httpContext.Request.PathBase, virtualPath, string.Empty); + + // Assert + Assert.Equal(expected, builder.ToString()); + } + + [Theory] + [MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))] + public void AppendPathAndFragment_AppendsFragments( + string appBase, + string virtualPath, + string expected) + { + // Arrange + var fragmentValue = "fragment-value"; + expected += $"#{fragmentValue}"; + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appBase, host: null, protocol: null); + var builder = new StringBuilder(); + + // Act + UrlHelperBase.AppendPathAndFragment(builder, httpContext.Request.PathBase, virtualPath, fragmentValue); + + // Assert + Assert.Equal(expected, builder.ToString()); + } + + [Theory] + [InlineData(null, null, null, "/", null, "/")] + [InlineData(null, null, null, "/Hello", null, "/Hello")] + [InlineData(null, null, null, "Hello", null, "/Hello")] + [InlineData("/", null, null, "", null, "/")] + [InlineData("/hello/", null, null, "/world", null, "/hello/world")] + [InlineData("/hello/", "https", "myhost", "/world", "fragment-value", "https://myhost/hello/world#fragment-value")] + public void GenerateUrl_FastAndSlowPathsReturnsExpected( + string appBase, + string protocol, + string host, + string virtualPath, + string fragment, + string expected) + { + // Arrange + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appBase, host, protocol); + var actionContext = CreateActionContext(httpContext); + var urlHelper = new TestUrlHelper(actionContext); + + // Act + var url = urlHelper.GenerateUrl(protocol, host, virtualPath, fragment); + + // Assert + Assert.Equal(expected, url); + } + + private static IServiceProvider CreateServices() + { + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(); + services.AddRouting(); + services + .AddSingleton() + .AddSingleton(UrlEncoder.Default); + + return services.BuildServiceProvider(); + } + + private static HttpContext CreateHttpContext( + IServiceProvider services, + string appRoot, + string host, + string protocol) + { + appRoot = string.IsNullOrEmpty(appRoot) ? string.Empty : appRoot; + host = string.IsNullOrEmpty(host) ? "localhost" : host; + + var context = new DefaultHttpContext(); + context.RequestServices = services; + context.Request.PathBase = new PathString(appRoot); + context.Request.Host = new HostString(host); + context.Request.Scheme = protocol; + return context; + } + + private static ActionContext CreateActionContext(HttpContext context) + { + return new ActionContext(context, new RouteData(), new ActionDescriptor()); + } + + private class TestUrlHelper : UrlHelperBase + { + public TestUrlHelper(ActionContext actionContext) : + base(actionContext) + { + } + + public override string Action(UrlActionContext actionContext) + { + throw new NotImplementedException(); + } + + public override string RouteUrl(UrlRouteContext routeContext) + { + throw new NotImplementedException(); + } + + public new string GenerateUrl( + string protocol, + string host, + string virtualPath, + string fragment) + { + return base.GenerateUrl( + protocol, + host, + virtualPath, + fragment); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs new file mode 100644 index 0000000000..c6e00c231e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs @@ -0,0 +1,585 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing +{ + public class UrlHelperExtensionsTest + { + [Fact] + public void Page_WithName_Works() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("/TestPage"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("/TestPage", value.Value); + }); + Assert.Null(actual.Host); + Assert.Null(actual.Protocol); + Assert.Null(actual.Fragment); + } + + public static TheoryData Page_WithNameAndRouteValues_WorksData + { + get => new TheoryData + { + { new { id = 10 } }, + { + new Dictionary + { + ["id"] = 10, + } + }, + { + new RouteValueDictionary + { + ["id"] = 10, + } + }, + }; + } + + [Theory] + [MemberData(nameof(Page_WithNameAndRouteValues_WorksData))] + public void Page_WithNameAndRouteValues_Works(object values) + { + // Arrange + UrlRouteContext actual = null; + var urlHelper = CreateMockUrlHelper(); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("/TestPage", values); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(10, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("/TestPage", value.Value); + }); + Assert.Null(actual.Host); + Assert.Null(actual.Protocol); + Assert.Null(actual.Fragment); + } + + [Fact] + public void Page_WithNameRouteValuesAndProtocol_Works() + { + // Arrange + UrlRouteContext actual = null; + var urlHelper = CreateMockUrlHelper(); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("/TestPage", pageHandler: null, values: new { id = 13 }, protocol: "https"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("/TestPage", value.Value); + }); + Assert.Equal("https", actual.Protocol); + Assert.Null(actual.Host); + Assert.Null(actual.Fragment); + } + + [Fact] + public void Page_WithNameRouteValuesProtocolAndHost_Works() + { + // Arrange + UrlRouteContext actual = null; + var urlHelper = CreateMockUrlHelper(); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("/TestPage", pageHandler: null, values: new { id = 13 }, protocol: "https", host: "mytesthost"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("/TestPage", value.Value); + }); + Assert.Equal("https", actual.Protocol); + Assert.Equal("mytesthost", actual.Host); + Assert.Null(actual.Fragment); + } + + [Fact] + public void Page_WithNameRouteValuesProtocolHostAndFragment_Works() + { + // Arrange + UrlRouteContext actual = null; + var urlHelper = CreateMockUrlHelper(); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("/TestPage", "test-handler", new { id = 13 }, "https", "mytesthost", "#toc"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("/TestPage", value.Value); + }, + value => + { + Assert.Equal("handler", value.Key); + Assert.Equal("test-handler", value.Value); + }); + Assert.Equal("https", actual.Protocol); + Assert.Equal("mytesthost", actual.Host); + Assert.Equal("#toc", actual.Fragment); + } + + [Fact] + public void Page_UsesAmbientRouteValue_WhenPageIsNull() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, new { id = 13 }); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }); + } + + [Fact] + public void Page_SetsHandlerToNull_IfValueIsNotSpecifiedInRouteValues() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "handler", "ambient-handler" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, new { id = 13 }); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("id", value.Key); + Assert.Equal(13, value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }, + value => + { + Assert.Equal("handler", value.Key); + Assert.Null(value.Value); + }); + } + + [Fact] + public void Page_UsesExplicitlySpecifiedHandlerValue() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "handler", "ambient-handler" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, "exact-handler", new { handler = "route-value-handler" }); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("handler", value.Key); + Assert.Equal("exact-handler", value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }); + } + + [Fact] + public void Page_UsesValueFromRouteValueIfPageHandlerIsNotExplicitySpecified() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "handler", "ambient-handler" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, pageHandler: null, values: new { handler = "route-value-handler" }); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("handler", value.Key); + Assert.Equal("route-value-handler", value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }); + } + + [Theory] + [InlineData("Sibling", "/Dir1/Dir2/Sibling")] + [InlineData("Dir3/Sibling", "/Dir1/Dir2/Dir3/Sibling")] + [InlineData("Dir4/Dir5/Index", "/Dir1/Dir2/Dir4/Dir5/Index")] + public void Page_CalculatesPathRelativeToViewEnginePath_WhenNotRooted(string pageName, string expected) + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData(); + var actionContext = GetActionContextForPage("/Dir1/Dir2/About"); + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page(pageName); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("page", value.Key); + Assert.Equal(expected, value.Value); + }); + } + + [Fact] + public void Page_CalculatesPathRelativeToViewEnginePath_ForIndexPagePaths() + { + // Arrange + var expected = "/Dir1/Dir2/Sibling"; + UrlRouteContext actual = null; + var actionContext = GetActionContextForPage("/Dir1/Dir2/"); + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("Sibling"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("page", value.Key); + Assert.Equal(expected, value.Value); + }); + } + + [Fact] + public void Page_CalculatesPathRelativeToViewEnginePath_WhenNotRooted_ForPageAtRoot() + { + // Arrange + var expected = "/SiblingName"; + UrlRouteContext actual = null; + var routeData = new RouteData(); + var actionContext = new ActionContext + { + ActionDescriptor = new ActionDescriptor + { + RouteValues = new Dictionary + { + { "page", "/Home" }, + }, + }, + RouteData = new RouteData + { + Values = + { + [ "page" ] = "/Home" + }, + }, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + urlHelper.Object.Page("SiblingName"); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values), + value => + { + Assert.Equal("page", value.Key); + Assert.Equal(expected, value.Value); + }); + } + + [Fact] + public void Page_Throws_IfRouteValueDoesNotIncludePageKey() + { + // Arrange + var expected = "SiblingName"; + UrlRouteContext actual = null; + var routeData = new RouteData(); + var actionContext = new ActionContext + { + RouteData = new RouteData(), + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act & Assert + var ex = Assert.Throws(() => urlHelper.Object.Page(expected)); + Assert.Equal($"The relative page path '{expected}' can only be used while executing a Razor Page. " + + "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.", ex.Message); + } + + [Fact] + public void Page_UsesAreaValueFromRouteValueIfSpecified() + { + // Arrange + UrlRouteContext actual = null; + var routeData = new RouteData + { + Values = + { + { "page", "ambient-page" }, + { "area", "ambient-area" }, + } + }; + var actionContext = new ActionContext + { + RouteData = routeData, + }; + + var urlHelper = CreateMockUrlHelper(actionContext); + urlHelper.Setup(h => h.RouteUrl(It.IsAny())) + .Callback((UrlRouteContext context) => actual = context); + + // Act + string page = null; + urlHelper.Object.Page(page, values: new { area = "specified-area" }); + + // Assert + urlHelper.Verify(); + Assert.NotNull(actual); + Assert.Null(actual.RouteName); + Assert.Collection(Assert.IsType(actual.Values).OrderBy(v => v.Key), + value => + { + Assert.Equal("area", value.Key); + Assert.Equal("specified-area", value.Value); + }, + value => + { + Assert.Equal("page", value.Key); + Assert.Equal("ambient-page", value.Value); + }); + } + + private static Mock CreateMockUrlHelper(ActionContext context = null) + { + if (context == null) + { + context = GetActionContextForPage("/Page"); + } + + var urlHelper = new Mock(); + urlHelper.SetupGet(h => h.ActionContext) + .Returns(context); + return urlHelper; + } + + private static ActionContext GetActionContextForPage(string page) + { + return new ActionContext + { + ActionDescriptor = new ActionDescriptor + { + RouteValues = new Dictionary + { + { "page", page }, + }, + }, + RouteData = new RouteData + { + Values = + { + [ "page" ] = page + }, + }, + }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs index b2754f56d3..63c88896ab 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs @@ -2,1729 +2,93 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.ObjectPool; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing { - public class UrlHelperTest + public class UrlHelperTest : UrlHelperTestBase { - [Theory] - [InlineData(null, null, null)] - [InlineData("/myapproot", null, null)] - [InlineData("", "/Home/About", "/Home/About")] - [InlineData("/myapproot", "/test", "/test")] - public void Content_ReturnsContentPath_WhenItDoesNotStartWithToken( - string appRoot, - string contentPath, - string expectedPath) + protected override IServiceProvider CreateServices() { - // Arrange - var httpContext = CreateHttpContext(CreateServices(), appRoot); - var actionContext = CreateActionContext(httpContext); - var urlHelper = CreateUrlHelper(actionContext); - - // Act - var path = urlHelper.Content(contentPath); - - // Assert - Assert.Equal(expectedPath, path); - } - - [Theory] - [InlineData(null, "~/Home/About", "/Home/About")] - [InlineData("/", "~/Home/About", "/Home/About")] - [InlineData("/", "~/", "/")] - [InlineData("/myapproot", "~/", "/myapproot/")] - [InlineData("", "~/Home/About", "/Home/About")] - [InlineData("/", "~", "/")] - [InlineData("/myapproot", "~/Content/bootstrap.css", "/myapproot/Content/bootstrap.css")] - public void Content_ReturnsAppRelativePath_WhenItStartsWithToken( - string appRoot, - string contentPath, - string expectedPath) - { - // Arrange - var httpContext = CreateHttpContext(CreateServices(), appRoot); - var actionContext = CreateActionContext(httpContext); - var urlHelper = CreateUrlHelper(actionContext); - - // Act - var path = urlHelper.Content(contentPath); - - // Assert - Assert.Equal(expectedPath, path); - } - - [Theory] - [InlineData(null)] - [InlineData("")] - [InlineData(" ")] - public void IsLocalUrl_ReturnsFalseOnEmpty(string url) - { - // Arrange - var helper = CreateUrlHelper(); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("/foo.html")] - [InlineData("/www.example.com")] - [InlineData("/")] - public void IsLocalUrl_AcceptsRootedUrls(string url) - { - // Arrange - var helper = CreateUrlHelper(); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("~/")] - [InlineData("~/foo.html")] - public void IsLocalUrl_AcceptsApplicationRelativeUrls(string url) - { - // Arrange - var helper = CreateUrlHelper(); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("foo.html")] - [InlineData("../foo.html")] - [InlineData("fold/foo.html")] - public void IsLocalUrl_RejectsRelativeUrls(string url) - { - // Arrange - var helper = CreateUrlHelper(); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http:/foo.html")] - [InlineData("hTtP:foo.html")] - [InlineData("http:/www.example.com")] - [InlineData("HtTpS:/www.example.com")] - public void IsLocalUrl_RejectValidButUnsafeRelativeUrls(string url) - { - // Arrange - var helper = CreateUrlHelper(); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http://www.mysite.com/appDir/foo.html")] - [InlineData("http://WWW.MYSITE.COM")] - public void IsLocalUrl_RejectsUrlsOnTheSameHost(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http://localhost/foobar.html")] - [InlineData("http://127.0.0.1/foobar.html")] - public void IsLocalUrl_RejectsUrlsOnLocalHost(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("https://www.mysite.com/")] - public void IsLocalUrl_RejectsUrlsOnTheSameHostButDifferentScheme(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http://www.example.com")] - [InlineData("https://www.example.com")] - [InlineData("hTtP://www.example.com")] - [InlineData("HtTpS://www.example.com")] - public void IsLocalUrl_RejectsUrlsOnDifferentHost(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http://///www.example.com/foo.html")] - [InlineData("https://///www.example.com/foo.html")] - [InlineData("HtTpS://///www.example.com/foo.html")] - [InlineData("http:///www.example.com/foo.html")] - [InlineData("http:////www.example.com/foo.html")] - public void IsLocalUrl_RejectsUrlsWithTooManySchemeSeparatorCharacters(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("//www.example.com")] - [InlineData("//www.example.com?")] - [InlineData("//www.example.com:80")] - [InlineData("//www.example.com/foobar.html")] - [InlineData("///www.example.com")] - [InlineData("//////www.example.com")] - public void IsLocalUrl_RejectsUrlsWithMissingSchemeName(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("http:\\\\www.example.com")] - [InlineData("http:\\\\www.example.com\\")] - [InlineData("/\\")] - [InlineData("/\\foo")] - public void IsLocalUrl_RejectsInvalidUrls(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("~//www.example.com")] - [InlineData("~//www.example.com?")] - [InlineData("~//www.example.com:80")] - [InlineData("~//www.example.com/foobar.html")] - [InlineData("~///www.example.com")] - [InlineData("~//////www.example.com")] - public void IsLocalUrl_RejectsTokenUrlsWithMissingSchemeName(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("~/\\")] - [InlineData("~/\\foo")] - public void IsLocalUrl_RejectsInvalidTokenUrls(string url) - { - // Arrange - var helper = CreateUrlHelper("www.mysite.com"); - - // Act - var result = helper.IsLocalUrl(url); - - // Assert - Assert.False(result); - } - - [Fact] - public void RouteUrlWithDictionary() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - })); - - // Assert - Assert.Equal("/app/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithEmptyHostName() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }), - protocol: "http", - host: string.Empty); - - // Assert - Assert.Equal("http://localhost/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithEmptyProtocol() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }), - protocol: string.Empty, - host: "foo.bar.com"); - - // Assert - Assert.Equal("http://foo.bar.com/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithNullProtocol() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }), - protocol: null, - host: "foo.bar.com"); - - // Assert - Assert.Equal("http://foo.bar.com/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithNullProtocolAndNullHostName() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }), - protocol: null, - host: null); - - // Assert - Assert.Equal("/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithObjectProperties() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl(new { Action = "newaction", Controller = "home2", id = "someid" }); - - // Assert - Assert.Equal("/app/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithProtocol() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }, - protocol: "https"); - - // Assert - Assert.Equal("https://localhost/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrl_WithUnicodeHost_DoesNotPunyEncodeTheHost() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }, - protocol: "https", - host: "pingüino"); - - // Assert - Assert.Equal("https://pingüino/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithRouteNameAndDefaults() - { - // Arrange - var services = CreateServices(); - var routeCollection = GetRouter(services, "MyRouteName", "any/url"); - var urlHelper = CreateUrlHelper("/app", routeCollection); - - // Act - var url = urlHelper.RouteUrl("MyRouteName"); - - // Assert - Assert.Equal("/app/any/url", url); - } - - [Fact] - public void RouteUrlWithRouteNameAndDictionary() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new RouteValueDictionary( - new - { - Action = "newaction", - Controller = "home2", - id = "someid" - })); - - // Assert - Assert.Equal("/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithRouteNameAndObjectProperties() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }); - - // Assert - Assert.Equal("/app/named/home2/newaction/someid", url); - } - - [Fact] - public void RouteUrlWithUrlRouteContext_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - var routeContext = new UrlRouteContext() - { - RouteName = "namedroute", - Values = new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }, - Fragment = "somefragment", - Host = "remotetown", - Protocol = "ftp" - }; - - // Act - var url = urlHelper.RouteUrl(routeContext); - - // Assert - Assert.Equal("ftp://remotetown/app/named/home2/newaction/someid#somefragment", url); - } - - [Fact] - public void RouteUrlWithAllParameters_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.RouteUrl( - routeName: "namedroute", - values: new - { - Action = "newaction", - Controller = "home2", - id = "someid" - }, - fragment: "somefragment", - host: "remotetown", - protocol: "https"); - - // Assert - Assert.Equal("https://remotetown/app/named/home2/newaction/someid#somefragment", url); - } - - [Fact] - public void UrlAction_RouteValuesAsDictionary_CaseSensitive() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // We're using a dictionary with a case-sensitive comparer and loading it with data - // using casings differently from the route. This should still successfully generate a link. - var dictionary = new Dictionary(); - var id = "suppliedid"; - var isprint = "true"; - dictionary["ID"] = id; - dictionary["isprint"] = isprint; - - // Act - var url = urlHelper.Action( - action: "contact", - controller: "home", - values: dictionary); - - // Assert - Assert.Equal(2, dictionary.Count); - Assert.Same(id, dictionary["ID"]); - Assert.Same(isprint, dictionary["isprint"]); - Assert.Equal("/app/home/contact/suppliedid?isprint=true", url); - } - - [Fact] - public void UrlAction_WithUnicodeHost_DoesNotPunyEncodeTheHost() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.Action( - action: "contact", - controller: "home", - values: null, - protocol: "http", - host: "pingüino"); - - // Assert - Assert.Equal("http://pingüino/app/home/contact", url); - } - - [Fact] - public void UrlRouteUrl_RouteValuesAsDictionary_CaseSensitive() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // We're using a dictionary with a case-sensitive comparer and loading it with data - // using casings differently from the route. This should still successfully generate a link. - var dict = new Dictionary(); - var action = "contact"; - var controller = "home"; - var id = "suppliedid"; - - dict["ACTION"] = action; - dict["Controller"] = controller; - dict["ID"] = id; - - // Act - var url = urlHelper.RouteUrl(routeName: "namedroute", values: dict); - - // Assert - Assert.Equal(3, dict.Count); - Assert.Same(action, dict["ACTION"]); - Assert.Same(controller, dict["Controller"]); - Assert.Same(id, dict["ID"]); - Assert.Equal("/app/named/home/contact/suppliedid", url); - } - - [Fact] - public void UrlActionWithUrlActionContext_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - var actionContext = new UrlActionContext() - { - Action = "contact", - Controller = "home3", - Values = new { id = "idone" }, - Protocol = "ftp", - Host = "remotelyhost", - Fragment = "somefragment" - }; - - // Act - var url = urlHelper.Action(actionContext); - - // Assert - Assert.Equal("ftp://remotelyhost/app/home3/contact/idone#somefragment", url); - } - - [Fact] - public void UrlActionWithAllParameters_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.Action( - controller: "home3", - action: "contact", - values: null, - protocol: "https", - host: "remotelyhost", - fragment: "somefragment"); - - // Assert - Assert.Equal("https://remotelyhost/app/home3/contact#somefragment", url); - } - - [Fact] - public void LinkWithAllParameters_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.Link( - "namedroute", - new - { - Action = "newaction", - Controller = "home", - id = "someid" - }); - - // Assert - Assert.Equal("http://localhost/app/named/home/newaction/someid", url); - } - - [Fact] - public void LinkWithNullRouteName_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var urlHelper = CreateUrlHelperWithRouteCollection(services, "/app"); - - // Act - var url = urlHelper.Link( - null, - new - { - Action = "newaction", - Controller = "home", - id = "someid" - }); - - // Assert - Assert.Equal("http://localhost/app/home/newaction/someid", url); - } - - [Fact] - public void LinkWithDefaultsAndNullRouteValues_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var routeCollection = GetRouter(services, "MyRouteName", "any/url"); - var urlHelper = CreateUrlHelper("/app", routeCollection); - - // Act - var url = urlHelper.Link("MyRouteName", null); - - // Assert - Assert.Equal("http://localhost/app/any/url", url); - } - - [Fact] - public void LinkWithCustomHostAndProtocol_ReturnsExpectedResult() - { - // Arrange - var services = CreateServices(); - var routeCollection = GetRouter(services, "MyRouteName", "any/url"); - var urlHelper = CreateUrlHelper("myhost", "https", routeCollection); - - // Act - var url = urlHelper.Link( - "namedroute", - new - { - Action = "newaction", - Controller = "home", - id = "someid" - }); - - // Assert - Assert.Equal("https://myhost/named/home/newaction/someid", url); - } - - // Regression test for aspnet/Mvc#2859 - [Fact] - public void Action_RouteValueInvalidation_DoesNotAffectActionAndController() - { - // Arrange - var services = CreateServices(); - var routeBuilder = CreateRouteBuilder(services); - - routeBuilder.MapRoute( - "default", - "{first}/{controller}/{action}", - new { second = "default", controller = "default", action = "default" }); - - var actionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext() - { - RequestServices = services, - }, - }; - - actionContext.RouteData = new RouteData(); - actionContext.RouteData.Values.Add("first", "a"); - actionContext.RouteData.Values.Add("controller", "Store"); - actionContext.RouteData.Values.Add("action", "Buy"); - actionContext.RouteData.Routers.Add(routeBuilder.Build()); - - var urlHelper = CreateUrlHelper(actionContext); - - // Act - // - // In this test the 'first' route value has changed, meaning that *normally* the - // 'controller' value could not be used. However 'controller' and 'action' are treated - // specially by UrlHelper. - var url = urlHelper.Action("Checkout", new { first = "b" }); - - // Assert - Assert.NotNull(url); - Assert.Equal("/b/Store/Checkout", url); - } - - // Regression test for aspnet/Mvc#2859 - [Fact] - public void Action_RouteValueInvalidation_AffectsOtherRouteValues() - { - // Arrange - var services = CreateServices(); - var routeBuilder = CreateRouteBuilder(services); - - routeBuilder.MapRoute( - "default", - "{first}/{second}/{controller}/{action}", - new { second = "default", controller = "default", action = "default" }); - - var actionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext() - { - RequestServices = services, - }, - }; - - actionContext.RouteData = new RouteData(); - actionContext.RouteData.Values.Add("first", "a"); - actionContext.RouteData.Values.Add("second", "x"); - actionContext.RouteData.Values.Add("controller", "Store"); - actionContext.RouteData.Values.Add("action", "Buy"); - actionContext.RouteData.Routers.Add(routeBuilder.Build()); - - var urlHelper = CreateUrlHelper(actionContext); - - // Act - // - // In this test the 'first' route value has changed, meaning that *normally* the - // 'controller' value could not be used. However 'controller' and 'action' are treated - // specially by UrlHelper. - // - // 'second' gets no special treatment, and picks up its default value instead. - var url = urlHelper.Action("Checkout", new { first = "b" }); - - // Assert - Assert.NotNull(url); - Assert.Equal("/b/default/Store/Checkout", url); - } - - // Regression test for aspnet/Mvc#2859 - [Fact] - public void Action_RouteValueInvalidation_DoesNotAffectActionAndController_ActionPassedInRouteValues() - { - // Arrange - var services = CreateServices(); - var routeBuilder = CreateRouteBuilder(services); - - routeBuilder.MapRoute( - "default", - "{first}/{controller}/{action}", - new { second = "default", controller = "default", action = "default" }); - - var actionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext() - { - RequestServices = services, - }, - }; - - actionContext.RouteData = new RouteData(); - actionContext.RouteData.Values.Add("first", "a"); - actionContext.RouteData.Values.Add("controller", "Store"); - actionContext.RouteData.Values.Add("action", "Buy"); - actionContext.RouteData.Routers.Add(routeBuilder.Build()); - - var urlHelper = CreateUrlHelper(actionContext); - - // Act - // - // In this test the 'first' route value has changed, meaning that *normally* the - // 'controller' value could not be used. However 'controller' and 'action' are treated - // specially by UrlHelper. - var url = urlHelper.Action(action: null, values: new { first = "b", action = "Checkout" }); - - // Assert - Assert.NotNull(url); - Assert.Equal("/b/Store/Checkout", url); - } - - public static TheoryData GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData => - new TheoryData - { - { null, "", "/" }, - { null, "/", "/" }, - { null, "Hello", "/Hello" }, - { null, "/Hello", "/Hello" }, - { "/", "", "/" }, - { "/", "hello", "/hello" }, - { "/", "/hello", "/hello" }, - { "/hello", "", "/hello" }, - { "/hello/", "", "/hello/" }, - { "/hello", "/", "/hello/" }, - { "/hello/", "world", "/hello/world" }, - { "/hello/", "/world", "/hello/world" }, - { "/hello/", "/world 123", "/hello/world 123" }, - { "/hello/", "/world%20123", "/hello/world%20123" }, - }; - - [Theory] - [MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))] - public void AppendPathAndFragment_HandlesLeadingAndTrailingSlashes( - string appBase, - string virtualPath, - string expected) - { - // Arrange - var router = Mock.Of(); - var pathData = new VirtualPathData(router, virtualPath) - { - VirtualPath = virtualPath - }; - var urlHelper = CreateUrlHelper(appBase, router); - var builder = new StringBuilder(); - - // Act - urlHelper.AppendPathAndFragment(builder, pathData, string.Empty); - - // Assert - Assert.Equal(expected, builder.ToString()); - } - - [Theory] - [MemberData(nameof(GeneratePathFromRoute_HandlesLeadingAndTrailingSlashesData))] - public void AppendPathAndFragment_AppendsFragments( - string appBase, - string virtualPath, - string expected) - { - // Arrange - var fragmentValue = "fragment-value"; - expected += $"#{fragmentValue}"; - var router = Mock.Of(); - var pathData = new VirtualPathData(router, virtualPath) - { - VirtualPath = virtualPath - }; - var urlHelper = CreateUrlHelper(appBase, router); - var builder = new StringBuilder(); - - // Act - urlHelper.AppendPathAndFragment(builder, pathData, fragmentValue); - - // Assert - Assert.Equal(expected, builder.ToString()); - } - - [Theory] - [InlineData(null, null, null, "/", null, "/")] - [InlineData(null, null, null, "/Hello", null, "/Hello")] - [InlineData(null, null, null, "Hello", null, "/Hello")] - [InlineData("/", null, null, "", null, "/")] - [InlineData("/hello/", null, null, "/world", null, "/hello/world")] - [InlineData("/hello/", "https", "myhost", "/world", "fragment-value", "https://myhost/hello/world#fragment-value")] - public void GenerateUrl_FastAndSlowPathsReturnsExpected( - string appBase, - string protocol, - string host, - string virtualPath, - string fragment, - string expected) - { - // Arrange - var router = Mock.Of(); - var pathData = new VirtualPathData(router, virtualPath) - { - VirtualPath = virtualPath - }; - var urlHelper = CreateUrlHelper(appBase, router); - - // Act - var url = urlHelper.GenerateUrl(protocol, host, pathData, fragment); - - // Assert - Assert.Equal(expected, url); - } - - [Fact] - public void GetUrlHelper_ReturnsSameInstance_IfAlreadyPresent() - { - // Arrange - var expectedUrlHelper = CreateUrlHelper(); - var httpContext = new Mock(); - var mockItems = new Dictionary - { - { typeof(IUrlHelper), expectedUrlHelper } - }; - httpContext.Setup(h => h.Items).Returns(mockItems); - - var actionContext = CreateActionContext(httpContext.Object, Mock.Of()); - var urlHelperFactory = new UrlHelperFactory(); - - // Act - var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); - - // Assert - Assert.Same(expectedUrlHelper, urlHelper); - } - - [Fact] - public void GetUrlHelper_CreatesNewInstance_IfNotAlreadyPresent() - { - // Arrange - var httpContext = new Mock(); - httpContext.Setup(h => h.Items).Returns(new Dictionary()); - - var actionContext = CreateActionContext(httpContext.Object, Mock.Of()); - var urlHelperFactory = new UrlHelperFactory(); - - // Act - var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); - - // Assert - Assert.NotNull(urlHelper); - Assert.Same(urlHelper, actionContext.HttpContext.Items[typeof(IUrlHelper)] as IUrlHelper); - } - - [Fact] - public void GetUrlHelper_CreatesNewInstance_IfExpectedTypeIsNotPresent() - { - // Arrange - var httpContext = new Mock(); - var mockItems = new Dictionary - { - { typeof(IUrlHelper), null } - }; - httpContext.Setup(h => h.Items).Returns(mockItems); - - var actionContext = CreateActionContext(httpContext.Object, Mock.Of()); - var urlHelperFactory = new UrlHelperFactory(); - - // Act - var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); - - // Assert - Assert.NotNull(urlHelper); - Assert.Same(urlHelper, actionContext.HttpContext.Items[typeof(IUrlHelper)] as IUrlHelper); - } - - [Fact] - public void Page_WithName_Works() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("/TestPage"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("/TestPage", value.Value); - }); - Assert.Null(actual.Host); - Assert.Null(actual.Protocol); - Assert.Null(actual.Fragment); - } - - public static TheoryData Page_WithNameAndRouteValues_WorksData - { - get => new TheoryData - { - { new { id = 10 } }, - { - new Dictionary - { - ["id"] = 10, - } - }, - { - new RouteValueDictionary - { - ["id"] = 10, - } - }, - }; - } - - [Theory] - [MemberData(nameof(Page_WithNameAndRouteValues_WorksData))] - public void Page_WithNameAndRouteValues_Works(object values) - { - // Arrange - UrlRouteContext actual = null; - var urlHelper = CreateMockUrlHelper(); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("/TestPage", values); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(10, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("/TestPage", value.Value); - }); - Assert.Null(actual.Host); - Assert.Null(actual.Protocol); - Assert.Null(actual.Fragment); - } - - [Fact] - public void Page_WithNameRouteValuesAndProtocol_Works() - { - // Arrange - UrlRouteContext actual = null; - var urlHelper = CreateMockUrlHelper(); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("/TestPage", pageHandler: null, values: new { id = 13 }, protocol: "https"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(13, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("/TestPage", value.Value); - }); - Assert.Equal("https", actual.Protocol); - Assert.Null(actual.Host); - Assert.Null(actual.Fragment); - } - - [Fact] - public void Page_WithNameRouteValuesProtocolAndHost_Works() - { - // Arrange - UrlRouteContext actual = null; - var urlHelper = CreateMockUrlHelper(); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("/TestPage", pageHandler: null, values: new { id = 13 }, protocol: "https", host: "mytesthost"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(13, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("/TestPage", value.Value); - }); - Assert.Equal("https", actual.Protocol); - Assert.Equal("mytesthost", actual.Host); - Assert.Null(actual.Fragment); - } - - [Fact] - public void Page_WithNameRouteValuesProtocolHostAndFragment_Works() - { - // Arrange - UrlRouteContext actual = null; - var urlHelper = CreateMockUrlHelper(); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("/TestPage", "test-handler", new { id = 13 }, "https", "mytesthost", "#toc"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(13, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("/TestPage", value.Value); - }, - value => - { - Assert.Equal("handler", value.Key); - Assert.Equal("test-handler", value.Value); - }); - Assert.Equal("https", actual.Protocol); - Assert.Equal("mytesthost", actual.Host); - Assert.Equal("#toc", actual.Fragment); - } - - [Fact] - public void Page_UsesAmbientRouteValue_WhenPageIsNull() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - string page = null; - urlHelper.Object.Page(page, new { id = 13 }); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(13, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("ambient-page", value.Value); - }); - } - - [Fact] - public void Page_SetsHandlerToNull_IfValueIsNotSpecifiedInRouteValues() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - { "handler", "ambient-handler" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - string page = null; - urlHelper.Object.Page(page, new { id = 13 }); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("id", value.Key); - Assert.Equal(13, value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("ambient-page", value.Value); - }, - value => - { - Assert.Equal("handler", value.Key); - Assert.Null(value.Value); - }); - } - - [Fact] - public void Page_UsesExplicitlySpecifiedHandlerValue() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - { "handler", "ambient-handler" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - string page = null; - urlHelper.Object.Page(page, "exact-handler", new { handler = "route-value-handler" }); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("handler", value.Key); - Assert.Equal("exact-handler", value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("ambient-page", value.Value); - }); - } - - [Fact] - public void Page_UsesValueFromRouteValueIfPageHandlerIsNotExplicitySpecified() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - { "handler", "ambient-handler" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - string page = null; - urlHelper.Object.Page(page, pageHandler: null, values: new { handler = "route-value-handler" }); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("handler", value.Key); - Assert.Equal("route-value-handler", value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("ambient-page", value.Value); - }); - } - - [Theory] - [InlineData("Sibling", "/Dir1/Dir2/Sibling")] - [InlineData("Dir3/Sibling", "/Dir1/Dir2/Dir3/Sibling")] - [InlineData("Dir4/Dir5/Index", "/Dir1/Dir2/Dir4/Dir5/Index")] - public void Page_CalculatesPathRelativeToViewEnginePath_WhenNotRooted(string pageName, string expected) - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData(); - var actionContext = GetActionContextForPage("/Dir1/Dir2/About"); - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page(pageName); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("page", value.Key); - Assert.Equal(expected, value.Value); - }); - } - - [Fact] - public void Page_CalculatesPathRelativeToViewEnginePath_ForIndexPagePaths() - { - // Arrange - var expected = "/Dir1/Dir2/Sibling"; - UrlRouteContext actual = null; - var actionContext = GetActionContextForPage("/Dir1/Dir2/"); - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("Sibling"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("page", value.Key); - Assert.Equal(expected, value.Value); - }); - } - - [Fact] - public void Page_CalculatesPathRelativeToViewEnginePath_WhenNotRooted_ForPageAtRoot() - { - // Arrange - var expected = "/SiblingName"; - UrlRouteContext actual = null; - var routeData = new RouteData(); - var actionContext = new ActionContext - { - ActionDescriptor = new ActionDescriptor - { - RouteValues = new Dictionary - { - { "page", "/Home" }, - }, - }, - RouteData = new RouteData - { - Values = - { - [ "page" ] = "/Home" - }, - }, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - urlHelper.Object.Page("SiblingName"); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values), - value => - { - Assert.Equal("page", value.Key); - Assert.Equal(expected, value.Value); - }); - } - - [Fact] - public void Page_Throws_IfRouteValueDoesNotIncludePageKey() - { - // Arrange - var expected = "SiblingName"; - UrlRouteContext actual = null; - var routeData = new RouteData(); - var actionContext = new ActionContext - { - RouteData = new RouteData(), - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act & Assert - var ex = Assert.Throws(() => urlHelper.Object.Page(expected)); - Assert.Equal($"The relative page path '{expected}' can only be used while executing a Razor Page. " + - "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.", ex.Message); - } - - [Fact] - public void Page_UsesAreaValueFromRouteValueIfSpecified() - { - // Arrange - UrlRouteContext actual = null; - var routeData = new RouteData - { - Values = - { - { "page", "ambient-page" }, - { "area", "ambient-area" }, - } - }; - var actionContext = new ActionContext - { - RouteData = routeData, - }; - - var urlHelper = CreateMockUrlHelper(actionContext); - urlHelper.Setup(h => h.RouteUrl(It.IsAny())) - .Callback((UrlRouteContext context) => actual = context); - - // Act - string page = null; - urlHelper.Object.Page(page, values: new { area = "specified-area" }); - - // Assert - urlHelper.Verify(); - Assert.NotNull(actual); - Assert.Null(actual.RouteName); - Assert.Collection(Assert.IsType(actual.Values).OrderBy(v => v.Key), - value => - { - Assert.Equal("area", value.Key); - Assert.Equal("specified-area", value.Value); - }, - value => - { - Assert.Equal("page", value.Key); - Assert.Equal("ambient-page", value.Value); - }); - } - - private static Mock CreateMockUrlHelper(ActionContext context = null) - { - if (context == null) - { - context = GetActionContextForPage("/Page"); - } - - var urlHelper = new Mock(); - urlHelper.SetupGet(h => h.ActionContext) - .Returns(context); - return urlHelper; - } - - private static HttpContext CreateHttpContext( - IServiceProvider services, - string appRoot) - { - var context = new DefaultHttpContext(); - context.RequestServices = services; - - context.Request.PathBase = new PathString(appRoot); - context.Request.Host = new HostString("localhost"); - - return context; - } - - private static ActionContext CreateActionContext(HttpContext context) - { - return CreateActionContext(context, (new Mock()).Object); - } - - private static ActionContext CreateActionContext(HttpContext context, IRouter router) - { - var routeData = new RouteData(); - routeData.Routers.Add(router); - - return new ActionContext(context, routeData, new ActionDescriptor()); - } - - private static UrlHelper CreateUrlHelper() - { - var services = CreateServices(); - var context = CreateHttpContext(services, string.Empty); - var actionContext = CreateActionContext(context); - - return new UrlHelper(actionContext); - } - - private static UrlHelper CreateUrlHelper(ActionContext context) - { - return new UrlHelper(context); - } - - private static UrlHelper CreateUrlHelper(string host) - { - var services = CreateServices(); - var context = CreateHttpContext(services, string.Empty); - context.Request.Host = new HostString(host); - - var actionContext = CreateActionContext(context); - - return new UrlHelper(actionContext); - } - - private static UrlHelper CreateUrlHelper(string host, string protocol, IRouter router) - { - var services = CreateServices(); - var context = CreateHttpContext(services, string.Empty); - context.Request.Host = new HostString(host); - context.Request.Scheme = protocol; - - var actionContext = CreateActionContext(context, router); - - return new UrlHelper(actionContext); - } - - private static TestUrlHelper CreateUrlHelper(string appBase, IRouter router) - { - var services = CreateServices(); - var context = CreateHttpContext(services, appBase); - var actionContext = CreateActionContext(context, router); - - return new TestUrlHelper(actionContext); - } - - private static UrlHelper CreateUrlHelperWithRouteCollection( - IServiceProvider services, - string appPrefix) - { - var routeCollection = GetRouter(services); - return CreateUrlHelper(appPrefix, routeCollection); - } - - private static IRouter GetRouter(IServiceProvider services) - { - return GetRouter(services, "mockRoute", "/mockTemplate"); - } - - private static IServiceProvider CreateServices() - { - var services = new ServiceCollection(); - services.AddOptions(); - services.AddLogging(); - services.AddRouting(); - services - .AddSingleton() - .AddSingleton(UrlEncoder.Default); - + var services = GetCommonServices(); return services.BuildServiceProvider(); } - private static IRouteBuilder CreateRouteBuilder(IServiceProvider services) + protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { - var app = new Mock(); - app - .SetupGet(a => a.ApplicationServices) - .Returns(services); - - return new RouteBuilder(app.Object) - { - DefaultHandler = new PassThroughRouter(), - }; + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appRoot, host, protocol); + var actionContext = CreateActionContext(httpContext); + var defaultRoutes = GetDefaultRoutes(services); + actionContext.RouteData.Routers.Add(defaultRoutes); + return new UrlHelper(actionContext); } - private static IRouter GetRouter( + protected override IUrlHelper CreateUrlHelperWithDefaultRoutes( + string appRoot, + string host, + string protocol, + string routeName, + string template) + { + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appRoot, host, protocol); + var actionContext = CreateActionContext(httpContext); + var router = GetDefaultRoutes(services, routeName, template); + actionContext.RouteData.Routers.Add(router); + return CreateUrlHelper(actionContext); + } + + protected override IUrlHelper CreateUrlHelper(ActionContext actionContext) + { + return new UrlHelper(actionContext); + } + + protected override IUrlHelper CreateUrlHelperWithDefaultRoutes(string appRoot, string host, string protocol) + { + var services = CreateServices(); + var context = CreateHttpContext(services, appRoot, host, protocol); + + var router = GetDefaultRoutes(services); + var actionContext = CreateActionContext(context); + actionContext.RouteData.Routers.Add(router); + + return CreateUrlHelper(actionContext); + } + + protected override IUrlHelper CreateUrlHelper( + string appRoot, + string host, + string protocol, + string routeName, + string template, + object defaults) + { + var services = CreateServices(); + var routeBuilder = CreateRouteBuilder(services); + routeBuilder.MapRoute( + routeName, + template, + defaults); + var router = routeBuilder.Build(); + var httpContext = CreateHttpContext(services, appRoot, host, protocol); + var actionContext = CreateActionContext(httpContext); + actionContext.RouteData.Routers.Add(router); + return CreateUrlHelper(actionContext); + } + + private static IRouter GetDefaultRoutes(IServiceProvider services) + { + return GetDefaultRoutes(services, "mockRoute", "/mockTemplate"); + } + + private static IRouter GetDefaultRoutes( IServiceProvider services, string mockRouteName, string mockTemplateValue) @@ -1756,24 +120,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing return routeBuilder.Build(); } - private static ActionContext GetActionContextForPage(string page) + private static IRouteBuilder CreateRouteBuilder(IServiceProvider services) { - return new ActionContext + var app = new Mock(); + app + .SetupGet(a => a.ApplicationServices) + .Returns(services); + + return new RouteBuilder(app.Object) { - ActionDescriptor = new ActionDescriptor - { - RouteValues = new Dictionary - { - { "page", page }, - }, - }, - RouteData = new RouteData - { - Values = - { - [ "page" ] = page - }, - }, + DefaultHandler = new PassThroughRouter(), }; } @@ -1790,22 +146,5 @@ namespace Microsoft.AspNetCore.Mvc.Routing return Task.FromResult(false); } } - - private class TestUrlHelper : UrlHelper - { - public TestUrlHelper(ActionContext actionContext) : - base(actionContext) - { - - } - public new string GenerateUrl(string protocol, string host, VirtualPathData pathData, string fragment) - { - return base.GenerateUrl( - protocol, - host, - pathData, - fragment); - } - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs new file mode 100644 index 0000000000..300efe8c4a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs @@ -0,0 +1,1007 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.ObjectPool; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public abstract class UrlHelperTestBase + { + [Theory] + [InlineData(null, null, null)] + [InlineData("/myapproot", null, null)] + [InlineData("", "/Home/About", "/Home/About")] + [InlineData("/myapproot", "/test", "/test")] + public void Content_ReturnsContentPath_WhenItDoesNotStartWithToken( + string appRoot, + string contentPath, + string expectedPath) + { + // Arrange + var urlHelper = CreateUrlHelper(appRoot); + + // Act + var path = urlHelper.Content(contentPath); + + // Assert + Assert.Equal(expectedPath, path); + } + + [Theory] + [InlineData(null, "~/Home/About", "/Home/About")] + [InlineData("/", "~/Home/About", "/Home/About")] + [InlineData("/", "~/", "/")] + [InlineData("/myapproot", "~/", "/myapproot/")] + [InlineData("", "~/Home/About", "/Home/About")] + [InlineData("/", "~", "/")] + [InlineData("/myapproot", "~/Content/bootstrap.css", "/myapproot/Content/bootstrap.css")] + public void Content_ReturnsAppRelativePath_WhenItStartsWithToken( + string appRoot, + string contentPath, + string expectedPath) + { + // Arrange + var urlHelper = CreateUrlHelper(appRoot); + + // Act + var path = urlHelper.Content(contentPath); + + // Assert + Assert.Equal(expectedPath, path); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void IsLocalUrl_ReturnsFalseOnEmpty(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("/foo.html")] + [InlineData("/www.example.com")] + [InlineData("/")] + public void IsLocalUrl_AcceptsRootedUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("~/")] + [InlineData("~/foo.html")] + public void IsLocalUrl_AcceptsApplicationRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("foo.html")] + [InlineData("../foo.html")] + [InlineData("fold/foo.html")] + public void IsLocalUrl_RejectsRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http:/foo.html")] + [InlineData("hTtP:foo.html")] + [InlineData("http:/www.example.com")] + [InlineData("HtTpS:/www.example.com")] + public void IsLocalUrl_RejectValidButUnsafeRelativeUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://www.mysite.com/appDir/foo.html")] + [InlineData("http://WWW.MYSITE.COM")] + public void IsLocalUrl_RejectsUrlsOnTheSameHost(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://localhost/foobar.html")] + [InlineData("http://127.0.0.1/foobar.html")] + public void IsLocalUrl_RejectsUrlsOnLocalHost(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("https://www.mysite.com/")] + public void IsLocalUrl_RejectsUrlsOnTheSameHostButDifferentScheme(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://www.example.com")] + [InlineData("https://www.example.com")] + [InlineData("hTtP://www.example.com")] + [InlineData("HtTpS://www.example.com")] + public void IsLocalUrl_RejectsUrlsOnDifferentHost(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http://///www.example.com/foo.html")] + [InlineData("https://///www.example.com/foo.html")] + [InlineData("HtTpS://///www.example.com/foo.html")] + [InlineData("http:///www.example.com/foo.html")] + [InlineData("http:////www.example.com/foo.html")] + public void IsLocalUrl_RejectsUrlsWithTooManySchemeSeparatorCharacters(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("//www.example.com")] + [InlineData("//www.example.com?")] + [InlineData("//www.example.com:80")] + [InlineData("//www.example.com/foobar.html")] + [InlineData("///www.example.com")] + [InlineData("//////www.example.com")] + public void IsLocalUrl_RejectsUrlsWithMissingSchemeName(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("http:\\\\www.example.com")] + [InlineData("http:\\\\www.example.com\\")] + [InlineData("/\\")] + [InlineData("/\\foo")] + public void IsLocalUrl_RejectsInvalidUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("~//www.example.com")] + [InlineData("~//www.example.com?")] + [InlineData("~//www.example.com:80")] + [InlineData("~//www.example.com/foobar.html")] + [InlineData("~///www.example.com")] + [InlineData("~//////www.example.com")] + public void IsLocalUrl_RejectsTokenUrlsWithMissingSchemeName(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData("~/\\")] + [InlineData("~/\\foo")] + public void IsLocalUrl_RejectsInvalidTokenUrls(string url) + { + // Arrange + var helper = CreateUrlHelper(appRoot: string.Empty, host: "www.mysite.com", protocol: null); + + // Act + var result = helper.IsLocalUrl(url); + + // Assert + Assert.False(result); + } + + [Fact] + public void RouteUrlWithDictionary() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + })); + + // Assert + Assert.Equal("/app/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithEmptyHostName() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }), + protocol: "http", + host: string.Empty); + + // Assert + Assert.Equal("http://localhost/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithEmptyProtocol() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }), + protocol: string.Empty, + host: "foo.bar.com"); + + // Assert + Assert.Equal("http://foo.bar.com/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithNullProtocol() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }), + protocol: null, + host: "foo.bar.com"); + + // Assert + Assert.Equal("http://foo.bar.com/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithNullProtocolAndNullHostName() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }), + protocol: null, + host: null); + + // Assert + Assert.Equal("/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithObjectProperties() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl(new { Action = "newaction", Controller = "home2", id = "someid" }); + + // Assert + Assert.Equal("/app/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithProtocol() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }, + protocol: "https"); + + // Assert + Assert.Equal("https://localhost/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrl_WithUnicodeHost_DoesNotPunyEncodeTheHost() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }, + protocol: "https", + host: "pingüino"); + + // Assert + Assert.Equal("https://pingüino/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithRouteNameAndDictionary() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new RouteValueDictionary( + new + { + Action = "newaction", + Controller = "home2", + id = "someid" + })); + + // Assert + Assert.Equal("/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithRouteNameAndObjectProperties() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }); + + // Assert + Assert.Equal("/app/named/home2/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithUrlRouteContext_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + var routeContext = new UrlRouteContext() + { + RouteName = "namedroute", + Values = new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }, + Fragment = "somefragment", + Host = "remotetown", + Protocol = "ftp" + }; + + // Act + var url = urlHelper.RouteUrl(routeContext); + + // Assert + Assert.Equal("ftp://remotetown/app/named/home2/newaction/someid#somefragment", url); + } + + [Fact] + public void RouteUrlWithAllParameters_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "namedroute", + values: new + { + Action = "newaction", + Controller = "home2", + id = "someid" + }, + fragment: "somefragment", + host: "remotetown", + protocol: "https"); + + // Assert + Assert.Equal("https://remotetown/app/named/home2/newaction/someid#somefragment", url); + } + + [Fact] + public void UrlAction_RouteValuesAsDictionary_CaseSensitive() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // We're using a dictionary with a case-sensitive comparer and loading it with data + // using casings differently from the route. This should still successfully generate a link. + var dictionary = new Dictionary(); + var id = "suppliedid"; + var isprint = "true"; + dictionary["ID"] = id; + dictionary["isprint"] = isprint; + + // Act + var url = urlHelper.Action( + action: "contact", + controller: "home", + values: dictionary); + + // Assert + Assert.Equal(2, dictionary.Count); + Assert.Same(id, dictionary["ID"]); + Assert.Same(isprint, dictionary["isprint"]); + Assert.Equal("/app/home/contact/suppliedid?isprint=true", url); + } + + [Fact] + public void UrlAction_WithUnicodeHost_DoesNotPunyEncodeTheHost() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.Action( + action: "contact", + controller: "home", + values: null, + protocol: "http", + host: "pingüino"); + + // Assert + Assert.Equal("http://pingüino/app/home/contact", url); + } + + [Fact] + public void UrlRouteUrl_RouteValuesAsDictionary_CaseSensitive() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // We're using a dictionary with a case-sensitive comparer and loading it with data + // using casings differently from the route. This should still successfully generate a link. + var dict = new Dictionary(); + var action = "contact"; + var controller = "home"; + var id = "suppliedid"; + + dict["ACTION"] = action; + dict["Controller"] = controller; + dict["ID"] = id; + + // Act + var url = urlHelper.RouteUrl(routeName: "namedroute", values: dict); + + // Assert + Assert.Equal(3, dict.Count); + Assert.Same(action, dict["ACTION"]); + Assert.Same(controller, dict["Controller"]); + Assert.Same(id, dict["ID"]); + Assert.Equal("/app/named/home/contact/suppliedid", url); + } + + [Fact] + public void UrlActionWithUrlActionContext_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + var actionContext = new UrlActionContext() + { + Action = "contact", + Controller = "home3", + Values = new { id = "idone" }, + Protocol = "ftp", + Host = "remotelyhost", + Fragment = "somefragment" + }; + + // Act + var url = urlHelper.Action(actionContext); + + // Assert + Assert.Equal("ftp://remotelyhost/app/home3/contact/idone#somefragment", url); + } + + [Fact] + public void UrlActionWithAllParameters_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.Action( + controller: "home3", + action: "contact", + values: null, + protocol: "https", + host: "remotelyhost", + fragment: "somefragment"); + + // Assert + Assert.Equal("https://remotelyhost/app/home3/contact#somefragment", url); + } + + [Fact] + public void LinkWithAllParameters_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.Link( + "namedroute", + new + { + Action = "newaction", + Controller = "home", + id = "someid" + }); + + // Assert + Assert.Equal("http://localhost/app/named/home/newaction/someid", url); + } + + [Fact] + public void LinkWithNullRouteName_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.Link( + null, + new + { + Action = "newaction", + Controller = "home", + id = "someid" + }); + + // Assert + Assert.Equal("http://localhost/app/home/newaction/someid", url); + } + + [Fact] + public void RouteUrlWithRouteNameAndDefaults() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes( + "/app", + host: null, + protocol: null, + routeName: "MyRouteName", + template: "any/url"); + + // Act + var url = urlHelper.RouteUrl("MyRouteName"); + + // Assert + Assert.Equal("/app/any/url", url); + } + + [Fact] + public void LinkWithDefaultsAndNullRouteValues_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes( + "/app", + host: null, + protocol: null, + routeName: "MyRouteName", + template: "any/url"); + + // Act + var url = urlHelper.Link("MyRouteName", null); + + // Assert + Assert.Equal("http://localhost/app/any/url", url); + } + + [Fact] + public void LinkWithCustomHostAndProtocol_ReturnsExpectedResult() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes( + string.Empty, + "myhost", + "https", + routeName: "MyRouteName", + template: "any/url"); + + // Act + var url = urlHelper.Link( + "namedroute", + new + { + Action = "newaction", + Controller = "home", + id = "someid" + }); + + // Assert + Assert.Equal("https://myhost/named/home/newaction/someid", url); + } + + [Fact] + public void GetUrlHelper_ReturnsSameInstance_IfAlreadyPresent() + { + // Arrange + var expectedUrlHelper = CreateUrlHelper(); + var httpContext = new Mock(); + httpContext.SetupGet(h => h.Features).Returns(new FeatureCollection()); + var mockItems = new Dictionary + { + { typeof(IUrlHelper), expectedUrlHelper } + }; + httpContext.Setup(h => h.Items).Returns(mockItems); + + var actionContext = CreateActionContext(httpContext.Object); + var urlHelperFactory = new UrlHelperFactory(); + + // Act + var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); + + // Assert + Assert.Same(expectedUrlHelper, urlHelper); + } + + [Fact] + public void GetUrlHelper_CreatesNewInstance_IfNotAlreadyPresent() + { + // Arrange + var httpContext = new Mock(); + httpContext.SetupGet(h => h.Features).Returns(new FeatureCollection()); + httpContext.Setup(h => h.Items).Returns(new Dictionary()); + + var actionContext = CreateActionContext(httpContext.Object); + var urlHelperFactory = new UrlHelperFactory(); + + // Act + var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); + + // Assert + Assert.NotNull(urlHelper); + Assert.Same(urlHelper, actionContext.HttpContext.Items[typeof(IUrlHelper)] as IUrlHelper); + } + + [Fact] + public void GetUrlHelper_CreatesNewInstance_IfExpectedTypeIsNotPresent() + { + // Arrange + var httpContext = new Mock(); + httpContext.SetupGet(h => h.Features).Returns(new FeatureCollection()); + var mockItems = new Dictionary + { + { typeof(IUrlHelper), null } + }; + httpContext.Setup(h => h.Items).Returns(mockItems); + + var actionContext = CreateActionContext(httpContext.Object); + var urlHelperFactory = new UrlHelperFactory(); + + // Act + var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); + + // Assert + Assert.NotNull(urlHelper); + Assert.Same(urlHelper, actionContext.HttpContext.Items[typeof(IUrlHelper)] as IUrlHelper); + } + + // Regression test for aspnet/Mvc#2859 + [Fact] + public void Action_RouteValueInvalidation_DoesNotAffectActionAndController() + { + // Arrange + var urlHelper = CreateUrlHelper( + appRoot: "", + host: null, + protocol: null, + "default", + "{first}/{controller}/{action}", + new { second = "default", controller = "default", action = "default" }); + + var routeData = urlHelper.ActionContext.RouteData; + routeData.Values.Add("first", "a"); + routeData.Values.Add("controller", "Store"); + routeData.Values.Add("action", "Buy"); + + // Act + // + // In this test the 'first' route value has changed, meaning that *normally* the + // 'controller' value could not be used. However 'controller' and 'action' are treated + // specially by UrlHelper. + var url = urlHelper.Action("Checkout", new { first = "b" }); + + // Assert + Assert.NotNull(url); + Assert.Equal("/b/Store/Checkout", url); + } + + // Regression test for aspnet/Mvc#2859 + [Fact] + public void Action_RouteValueInvalidation_AffectsOtherRouteValues() + { + // Arrange + var urlHelper = CreateUrlHelper( + appRoot: "", + host: null, + protocol: null, + "default", + "{first}/{second}/{controller}/{action}", + new { second = "default", controller = "default", action = "default" }); + + var routeData = urlHelper.ActionContext.RouteData; + routeData.Values.Add("first", "a"); + routeData.Values.Add("second", "x"); + routeData.Values.Add("controller", "Store"); + routeData.Values.Add("action", "Buy"); + + // Act + // + // In this test the 'first' route value has changed, meaning that *normally* the + // 'controller' value could not be used. However 'controller' and 'action' are treated + // specially by UrlHelper. + // + // 'second' gets no special treatment, and picks up its default value instead. + var url = urlHelper.Action("Checkout", new { first = "b" }); + + // Assert + Assert.NotNull(url); + Assert.Equal("/b/default/Store/Checkout", url); + } + + // Regression test for aspnet/Mvc#2859 + [Fact] + public void Action_RouteValueInvalidation_DoesNotAffectActionAndController_ActionPassedInRouteValues() + { + // Arrange + var urlHelper = CreateUrlHelper( + appRoot: "", + host: null, + protocol: null, + "default", + "{first}/{controller}/{action}", + new { second = "default", controller = "default", action = "default" }); + + var routeData = urlHelper.ActionContext.RouteData; + routeData.Values.Add("first", "a"); + routeData.Values.Add("controller", "Store"); + routeData.Values.Add("action", "Buy"); + + // Act + // + // In this test the 'first' route value has changed, meaning that *normally* the + // 'controller' value could not be used. However 'controller' and 'action' are treated + // specially by UrlHelper. + var url = urlHelper.Action(action: null, values: new { first = "b", action = "Checkout" }); + + // Assert + Assert.NotNull(url); + Assert.Equal("/b/Store/Checkout", url); + } + + + protected abstract IServiceProvider CreateServices(); + + protected abstract IUrlHelper CreateUrlHelper(ActionContext actionContext); + + protected abstract IUrlHelper CreateUrlHelperWithDefaultRoutes( + string appRoot, + string host, + string protocol); + + protected abstract IUrlHelper CreateUrlHelperWithDefaultRoutes( + string appRoot, + string host, + string protocol, + string routeName, + string template); + + protected abstract IUrlHelper CreateUrlHelper( + string appRoot, + string host, + string protocol, + string routeName, + string template, + object defaults); + + protected virtual IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) + { + appRoot = string.IsNullOrEmpty(appRoot) ? string.Empty : appRoot; + host = string.IsNullOrEmpty(host) ? "localhost" : host; + + var services = CreateServices(); + var httpContext = CreateHttpContext(services, appRoot, host, protocol); + + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + return CreateUrlHelper(actionContext); + } + + protected virtual ActionContext CreateActionContext(HttpContext httpContext, RouteData routeData = null) + { + routeData = routeData ?? new RouteData(); + return new ActionContext(httpContext, routeData, new ActionDescriptor()); + } + + protected virtual HttpContext CreateHttpContext( + IServiceProvider services, + string appRoot, + string host, + string protocol) + { + appRoot = string.IsNullOrEmpty(appRoot) ? string.Empty : appRoot; + host = string.IsNullOrEmpty(host) ? "localhost" : host; + + var context = new DefaultHttpContext(); + context.RequestServices = services; + context.Request.PathBase = new PathString(appRoot); + context.Request.Host = new HostString(host); + context.Request.Scheme = protocol; + return context; + } + + protected IServiceCollection GetCommonServices() + { + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(); + services.AddRouting(); + services + .AddSingleton() + .AddSingleton(UrlEncoder.Default); + return services; + } + + private IUrlHelper CreateUrlHelper(string appRoot = "") + { + return CreateUrlHelper(appRoot, host: null, protocol: null); + } + + private IUrlHelper CreateUrlHelperWithDefaultRoutes() + { + return CreateUrlHelperWithDefaultRoutes(appRoot: "/app", host: null, protocol: null); + } + } +} From 58aa16ee696a00fb40d91a4011c2c5f890ea983d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 19 Jun 2018 23:24:15 +1200 Subject: [PATCH 062/316] Centralize routing and dispatching functional test logic (#7938) --- Mvc.sln | 15 - .../DispatchingTests.cs | 1169 +-------------- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 1 - .../RoutingTests.cs | 1250 +--------------- .../RoutingTestsBase.cs | 1259 +++++++++++++++++ .../Areas/Admin/UserManagementController.cs | 25 - .../Areas/Order/OrderController.cs | 25 - .../Areas/Travel/FlightController.cs | 30 - .../Areas/Travel/HomeController.cs | 29 - .../Areas/Travel/RailController.cs | 30 - .../Controllers/BanksController.cs | 52 - .../Controllers/BlogController.cs | 29 - .../Controllers/CompanyController.cs | 63 - .../Controllers/EmployeeController.cs | 73 - .../Controllers/FriendsController.cs | 31 - .../Controllers/HomeController.cs | 39 - .../Controllers/MapsController.cs | 53 - .../Controllers/OrderController.cs | 36 - .../Controllers/StoreController.cs | 43 - .../Controllers/TeamController.cs | 72 - .../DispatchingWebSite.csproj | 15 - .../DispatchingWebSite/HttpMergeAttribute.cs | 34 - .../TestResponseGenerator.cs | 61 - test/WebSites/DispatchingWebSite/readme.md | 4 - test/WebSites/RoutingWebSite/Program.cs | 26 + test/WebSites/RoutingWebSite/Startup.cs | 15 - .../StartupWithDispatching.cs} | 38 +- 27 files changed, 1368 insertions(+), 3149 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs delete mode 100644 test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/BanksController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/BlogController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/HomeController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/MapsController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/OrderController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/StoreController.cs delete mode 100644 test/WebSites/DispatchingWebSite/Controllers/TeamController.cs delete mode 100644 test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj delete mode 100644 test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs delete mode 100644 test/WebSites/DispatchingWebSite/TestResponseGenerator.cs delete mode 100644 test/WebSites/DispatchingWebSite/readme.md create mode 100644 test/WebSites/RoutingWebSite/Program.cs rename test/WebSites/{DispatchingWebSite/Startup.cs => RoutingWebSite/StartupWithDispatching.cs} (67%) diff --git a/Mvc.sln b/Mvc.sln index eef6a57f11..f03885947a 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -170,8 +170,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "t EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DispatchingWebSite", "test\WebSites\DispatchingWebSite\DispatchingWebSite.csproj", "{ABB3737F-E518-4E40-8A9C-F3281D610E8F}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -896,18 +894,6 @@ Global {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|Mixed Platforms.Build.0 = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.ActiveCfg = Release|Any CPU {51E3E785-A9D1-4196-BAFE-A17FF4304B89}.Release|x86.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.ActiveCfg = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Debug|x86.Build.0 = Debug|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Any CPU.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.ActiveCfg = Release|Any CPU - {ABB3737F-E518-4E40-8A9C-F3281D610E8F}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -977,7 +963,6 @@ Global {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {ABB3737F-E518-4E40-8A9C-F3281D610E8F} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs index 99d7472b60..2d5dcb2749 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -1,285 +1,68 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class DispatchingTests : IClassFixture> + public class DispatchingTests : RoutingTestsBase { - public DispatchingTests(MvcTestFixture fixture) + public DispatchingTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/Index", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() + public override Task ConventionalRoutedController_ActionIsReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_NonActionIsNotReachable() + public override Task ConventionalRoutedController_ActionIsReachable_WithDefaults() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_InArea_ActionIsReachable() + public override Task ConventionalRoutedController_NonActionIsNotReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "area", "Travel" }, - { "controller", "Flight" }, - { "action", "Index" }, - }, - result.RouteValues); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + public override Task ConventionalRoutedController_InArea_ActionIsReachable() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + return Task.CompletedTask; + } - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + [Fact(Skip = "Conventional routing WIP")] + public override Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + return Task.CompletedTask; } [Theory(Skip = "Conventional routing WIP")] [InlineData("", "/Home/OptionalPath/default")] [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] - public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) + public override Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("ListProducts", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Store"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "ListProducts"), - result.RouteValues); - } - - [Theory] - [InlineData("Get", "/Friends")] - [InlineData("Get", "/Friends/Peter")] - [InlineData("Delete", "/Friends")] - public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( - string method, - string url) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); - - // Assert - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(url, result.ExpectedUrls); - Assert.Equal("Friends", result.Controller); - Assert.Equal(method, result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Friends"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", method), - result.RouteValues); - - if (result.RouteValues.ContainsKey("id")) - { - Assert.Contains( - new KeyValuePair("id", "Peter"), - result.RouteValues); - } - } - - [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] - public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( - string method, - string url) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); - - // Assert - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("http://localhost/api/v1/Maps")] [InlineData("http://localhost/api/v2/Maps")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) + public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) { - // Arrange & Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v1/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() + public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() { - // Arrange - var url = "http://localhost/api/v2/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Post", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() - { - // Arrange - var url = "http://localhost/api/v1/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] @@ -287,269 +70,19 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("http://localhost/api/v2/Maps/5", "PUT")] [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( + public override Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( string url, string method) { - // Arrange & Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Update", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps/PartialUpdate/5", - "/api/v2/Maps/PartialUpdate/5" - }, - result.ExpectedUrls); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("http://localhost/Banks/Get/5")] [InlineData("http://localhost/Bank/Get/5")] - public async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) + public override Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Banks", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/Bank/Get/5", - "/Bank/Get/5" - }, - result.ExpectedUrls); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( - string url, - string method) - { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // The url would be /Store/ListProducts with conventional routes - [Fact] - public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/ListProducts"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // There's two actions at this URL - but attribute routes go in the route table - // first. - [Fact] - public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/About"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/About", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("About", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); - Assert.Equal("Blog", result.Controller); - Assert.Equal("Edit", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Blog"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "Edit"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("postId", "5"), - result.RouteValues); - } - - // There's no [HttpGet] on the action here. - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Employee"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - } - - // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller - // and a test that verifies that if you define another action with a specific verb we'll route to that - // more specific action. - [Theory] - [InlineData("PUT")] - [InlineData("POST")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); - - // 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("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("Orders", result.Action); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); - - // 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/Employee/5/Salary", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("Salary", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); - - // 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("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("GetOrders", result.Action); - } - - // There's no [HttpGet] on the action here. - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); - - // 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/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateEmployee", result.Action); - } - - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); - - // 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/Employee/Manager", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateManager", result.Action); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] @@ -557,703 +90,145 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("PATCH", "Bank")] [InlineData("PUT", "Bank/Update")] [InlineData("PATCH", "Bank/Update")] - public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) + public override Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) { - // Arrange - var expectedUrl = "/Bank/Update"; - var message = new HttpRequestMessage(new HttpMethod(verb), "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(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal("UpdateBank", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/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.Contains("/api/Employee/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("MergeEmployee", result.Action); - } - - // There's an [HttpGet] with its own template on the action here. - [Theory] - [InlineData("GET", "GetAdministrator")] - [InlineData("DELETE", "DeleteAdministrator")] - public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); - - // 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/Employee/5/Administrator", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal(action, result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Manager/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Manager/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("GetManager", result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Team/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Team/5", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganization", result.Action); - - Assert.Contains( - new KeyValuePair("teamId", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Teams"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Teams", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganizations", result.Action); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() + public override Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Organization/5"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Club/5", response); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() + public override Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Teams/AllOrganizations", response); - } - - [Theory] - [InlineData("", "/TeamName/DefaultName")] - [InlineData("CustomName", "/TeamName/CustomName")] - public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) - { - // Arrange & Act - var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); - - // Assert - Assert.NotNull(body); - var result = JsonConvert.DeserializeObject(body); - Assert.Single(result.ExpectedUrls, expected); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToSelf() + public override Task AttributeRoutedAction_LinkToSelf() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithAmbientController() + public override Task AttributeRoutedAction_LinkWithAmbientController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee/5", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToAttributeRoutedController() + public override Task AttributeRoutedAction_LinkToAttributeRoutedController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/Blog/ShowPosts", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToConventionalController() + public override Task AttributeRoutedAction_LinkToConventionalController() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Theory(Skip = "URL generation WIP")] [InlineData("GET", "Get")] [InlineData("PUT", "Put")] - public async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( + public override Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( string method, string actionName) { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/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("Company", result.Controller); - Assert.Equal(actionName, result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("Company", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + public override Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() { - // Arrange & Act - var response = await Client.DeleteAsync("http://localhost/api/Company/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("Delete", result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("RemoveCompany", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() + public override Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() { - // Arrange - var url = LinkFrom("http://localhost") - .To(new { id = 5 }); - - // Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetEmployees", result.Action); - - Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); - Assert.Null(result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() + public override Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetDepartments", result.Action); - - Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); - Assert.Equal("Departments", result.RouteName); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_LinkToArea() + public override Task ConventionalRoutedAction_LinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() + public override Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_StaysInArea() + public override Task ConventionalRoutedAction_InArea_StaysInArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_LinkToArea() + public override Task AttributeRoutedAction_LinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/api/Employee") - .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() + public override Task AttributeRoutedAction_InArea_ImplicitLinkToArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() + public override Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Index", controller = "Flight", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() + public override Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Rail", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); + return Task.CompletedTask; } [Fact(Skip = "Conventional routing WIP")] - public async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() + public override Task ConventionalRoutedAction_InArea_LinkToAnotherArea() { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); + return Task.CompletedTask; } [Fact(Skip = "URL generation WIP")] - public async Task AttributeRoutedAction_InArea_LinkToAnotherArea() + public override Task AttributeRoutedAction_InArea_LinkToAnotherArea() { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Theory] - [InlineData("/Bank/Deposit", "PUT", "Deposit")] - [InlineData("/Bank/Deposit", "POST", "Deposit")] - [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] - [InlineData("/Bank/Deposit/5", "POST", "Deposit")] - [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal(actionName, result.Action); - } - - // These verbs don't match - [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("/Order/Add/1", "GET", "Add")] - [InlineData("/Order/Add", "POST", "Add")] - [InlineData("/Order/Edit/1", "PUT", "Edit")] - [InlineData("/Order/GetOrder", "GET", "GetOrder")] - public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Order", result.Controller); - Assert.Equal(actionName, result.Action); - } - - private static LinkBuilder LinkFrom(string url) - { - return new LinkBuilder(url); - } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - 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; } - } - - private class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary(); - Values.Add("link", string.Empty); - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } + return Task.CompletedTask; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 917d2938cb..b47eec39bf 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -31,7 +31,6 @@ - diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index e2917d8f1e..3a3e6527dd 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -1,1259 +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; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class RoutingTests : IClassFixture> + public class RoutingTests : RoutingTestsBase { public RoutingTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact] - public async Task ConventionalRoutedController_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/Index", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/", result.ExpectedUrls); - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "controller", "Home" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_NonActionIsNotReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Fact] - public async Task ConventionalRoutedController_InArea_ActionIsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - Assert.Equal( - new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "area", "Travel" }, - { "controller", "Flight" }, - { "action", "Index" }, - }, - result.RouteValues); - } - - [Fact] - public async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("", "/Home/OptionalPath/default")] - [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] - public async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("ListProducts", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Store"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "ListProducts"), - result.RouteValues); - } - - [Theory] - [InlineData("Get", "/Friends")] - [InlineData("Get", "/Friends/Peter")] - [InlineData("Delete", "/Friends")] - public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( - string method, - string url) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); - - // Assert - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(url, result.ExpectedUrls); - Assert.Equal("Friends", result.Controller); - Assert.Equal(method, result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Friends"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", method), - result.RouteValues); - - if (result.RouteValues.ContainsKey("id")) - { - Assert.Contains( - new KeyValuePair("id", "Peter"), - result.RouteValues); - } - } - - [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] - public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( - string method, - string url) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); - - // Assert - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps")] - [InlineData("http://localhost/api/v2/Maps")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) - { - // Arrange & Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v1/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() - { - // Arrange - var url = "http://localhost/api/v2/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Post", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps", - "/api/v2/Maps" - }, - result.ExpectedUrls); - } - - [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() - { - // Arrange - var url = "http://localhost/api/v1/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/5", "PUT")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( - string url, - string method) - { - // Arrange & Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Maps", result.Controller); - Assert.Equal("Update", result.Action); - - Assert.Equal(new string[] - { - "/api/v2/Maps/PartialUpdate/5", - "/api/v2/Maps/PartialUpdate/5" - }, - result.ExpectedUrls); - } - - [Theory] - [InlineData("http://localhost/Banks/Get/5")] - [InlineData("http://localhost/Bank/Get/5")] - public async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) - { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Banks", result.Controller); - Assert.Equal("Get", result.Action); - - Assert.Equal(new string[] - { - "/Bank/Get/5", - "/Bank/Get/5" - }, - result.ExpectedUrls); - } - - [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( - string url, - string method) - { - // Arrange - var expectedUrl = new Uri(url).AbsolutePath; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // The url would be /Store/ListProducts with conventional routes - [Fact] - public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Store/ListProducts"); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - // There's two actions at this URL - but attribute routes go in the route table - // first. - [Fact] - public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Home/About"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Home/About", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("About", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); - Assert.Equal("Blog", result.Controller); - Assert.Equal("Edit", result.Action); - - Assert.Contains( - new KeyValuePair("controller", "Blog"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("action", "Edit"), - result.RouteValues); - - Assert.Contains( - new KeyValuePair("postId", "5"), - result.RouteValues); - } - - // There's no [HttpGet] on the action here. - [Fact] - public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Employee"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/api/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - } - - // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller - // and a test that verifies that if you define another action with a specific verb we'll route to that - // more specific action. - [Theory] - [InlineData("PUT")] - [InlineData("POST")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); - - // 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("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("Orders", result.Action); - } - - [Theory] - [InlineData("GET")] - [InlineData("POST")] - [InlineData("PUT")] - [InlineData("PATCH")] - [InlineData("DELETE")] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); - - // 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/Employee/5/Salary", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("Salary", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() - { - // Arrange - var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); - - // 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("/Store/Shop/Orders", result.ExpectedUrls); - Assert.Equal("Store", result.Controller); - Assert.Equal("GetOrders", result.Action); - } - - // There's no [HttpGet] on the action here. - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); - - // 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/Employee", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateEmployee", result.Action); - } - - [Theory] - [InlineData("PUT")] - [InlineData("PATCH")] - public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); - - // 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/Employee/Manager", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("UpdateManager", result.Action); - } - - [Theory] - [InlineData("PUT", "Bank")] - [InlineData("PATCH", "Bank")] - [InlineData("PUT", "Bank/Update")] - [InlineData("PATCH", "Bank/Update")] - public async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) - { - // Arrange - var expectedUrl = "/Bank/Update"; - var message = new HttpRequestMessage(new HttpMethod(verb), "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(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal("UpdateBank", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/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.Contains("/api/Employee/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("MergeEmployee", result.Action); - } - - // There's an [HttpGet] with its own template on the action here. - [Theory] - [InlineData("GET", "GetAdministrator")] - [InlineData("DELETE", "DeleteAdministrator")] - public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); - - // 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/Employee/5/Administrator", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal(action, result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Manager/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Manager/5", result.ExpectedUrls); - Assert.Equal("Employee", result.Controller); - Assert.Equal("GetManager", result.Action); - - Assert.Contains( - new KeyValuePair("id", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Team/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Team/5", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganization", result.Action); - - Assert.Contains( - new KeyValuePair("teamId", "5"), - result.RouteValues); - } - - [Fact] - public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Teams"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains("/Teams", result.ExpectedUrls); - Assert.Equal("Team", result.Controller); - Assert.Equal("GetOrganizations", result.Action); - } - - [Fact] - public async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Organization/5"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Club/5", response); - } - - [Fact] - public async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() - { - // Arrange & Act - var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); - - // Assert - Assert.NotNull(response); - Assert.Equal("/Teams/AllOrganizations", response); - } - - [Theory] - [InlineData("", "/TeamName/DefaultName")] - [InlineData("CustomName", "/TeamName/CustomName")] - public async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) - { - // Arrange & Act - var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); - - // Assert - Assert.NotNull(body); - var result = JsonConvert.DeserializeObject(body); - Assert.Single(result.ExpectedUrls, expected); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToSelf() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithAmbientController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/api/Employee/5", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToAttributeRoutedController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/Blog/ShowPosts", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToConventionalController() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/", result.Link); - } - - [Theory] - [InlineData("GET", "Get")] - [InlineData("PUT", "Put")] - public async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( - string method, - string actionName) - { - // Arrange - var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/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("Company", result.Controller); - Assert.Equal(actionName, result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("Company", result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() - { - // Arrange & Act - var response = await Client.DeleteAsync("http://localhost/api/Company/5"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("Delete", result.Action); - - Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); - Assert.Equal("RemoveCompany", result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() - { - // Arrange - var url = LinkFrom("http://localhost") - .To(new { id = 5 }); - - // Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetEmployees", result.Action); - - Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); - Assert.Null(result.RouteName); - } - - [Fact] - public async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Company", result.Controller); - Assert.Equal("GetDepartments", result.Action); - - Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); - Assert.Equal("Departments", result.RouteName); - } - - [Fact] - public async Task ConventionalRoutedAction_LinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/") - .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Home", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight/BuyTickets", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_StaysInArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_LinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/api/Employee") - .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Employee", result.Controller); - Assert.Equal("List", result.Action); - - Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") - .To(new { action = "Index", controller = "Home", area = "" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Schedule", result.Action); - - Assert.Equal("/", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Index", controller = "Flight", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Flight", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "Index", controller = "Rail", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/ContosoCorp/Trains", result.Link); - } - - [Fact] - public async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Fact] - public async Task AttributeRoutedAction_InArea_LinkToAnotherArea() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Admin/Users/All", result.Link); - } - - [Theory] - [InlineData("/Bank/Deposit", "PUT", "Deposit")] - [InlineData("/Bank/Deposit", "POST", "Deposit")] - [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] - [InlineData("/Bank/Deposit/5", "POST", "Deposit")] - [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Banks", result.Controller); - Assert.Equal(actionName, result.Action); - } - - // These verbs don't match - [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("/Order/Add/1", "GET", "Add")] - [InlineData("/Order/Add", "POST", "Add")] - [InlineData("/Order/Edit/1", "PUT", "Edit")] - [InlineData("/Order/GetOrder", "GET", "GetOrder")] - public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Contains(path, result.ExpectedUrls); - Assert.Equal("Order", result.Controller); - Assert.Equal(actionName, result.Action); - } - - private static LinkBuilder LinkFrom(string url) - { - return new LinkBuilder(url); - } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - 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; } - } - - private class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary(); - Values.Add("link", string.Empty); - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs new file mode 100644 index 0000000000..a5ea33a0f1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -0,0 +1,1259 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class RoutingTestsBase : IClassFixture> where TStartup : class + { + protected RoutingTestsBase(MvcTestFixture fixture) + { + Client = fixture.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact] + public virtual async Task ConventionalRoutedController_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/Index", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_ActionIsReachable_WithDefaults() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/", result.ExpectedUrls); + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "controller", "Home" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_NonActionIsNotReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/NotAnAction"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public virtual async Task ConventionalRoutedController_InArea_ActionIsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Travel/Flight/Index", result.ExpectedUrls); + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal( + new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "area", "Travel" }, + { "controller", "Flight" }, + { "action", "Index" }, + }, + result.RouteValues); + } + + [Fact] + public virtual async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("", "/Home/OptionalPath/default")] + [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] + public virtual async Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/OptionalPath/" + optionalSegment); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact] + public async Task AttributeRoutedAction_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/Shop/Products"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Store/Shop/Products", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("ListProducts", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Store"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "ListProducts"), + result.RouteValues); + } + + [Theory] + [InlineData("Get", "/Friends")] + [InlineData("Get", "/Friends/Peter")] + [InlineData("Delete", "/Friends")] + public async Task AttributeRoutedAction_AcceptRequestsWithValidMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(url, result.ExpectedUrls); + Assert.Equal("Friends", result.Controller); + Assert.Equal(method, result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Friends"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", method), + result.RouteValues); + + if (result.RouteValues.ContainsKey("id")) + { + Assert.Contains( + new KeyValuePair("id", "Peter"), + result.RouteValues); + } + } + + [Theory] + [InlineData("Post", "/Friends")] + [InlineData("Put", "/Friends")] + [InlineData("Patch", "/Friends")] + [InlineData("Options", "/Friends")] + [InlineData("Head", "/Friends")] + public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps")] + [InlineData("http://localhost/api/v2/Maps")] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) + { + // Arrange & Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v1/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() + { + // Arrange + var url = "http://localhost/api/v2/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(HttpMethod.Post, url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Post", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps", + "/api/v2/Maps" + }, + result.ExpectedUrls); + } + + [Fact] + public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + { + // Arrange + var url = "http://localhost/api/v1/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/5", "PUT")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( + string url, + string method) + { + // Arrange & Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Maps", result.Controller); + Assert.Equal("Update", result.Action); + + Assert.Equal(new string[] + { + "/api/v2/Maps/PartialUpdate/5", + "/api/v2/Maps/PartialUpdate/5" + }, + result.ExpectedUrls); + } + + [Theory] + [InlineData("http://localhost/Banks/Get/5")] + [InlineData("http://localhost/Bank/Get/5")] + public virtual async Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Banks", result.Controller); + Assert.Equal("Get", result.Action); + + Assert.Equal(new string[] + { + "/Bank/Get/5", + "/Bank/Get/5" + }, + result.ExpectedUrls); + } + + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + string url, + string method) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + // The url would be /Store/ListProducts with conventional routes + [Fact] + public async Task AttributeRoutedAction_IsNotReachableWithTraditionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Store/ListProducts"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + // There's two actions at this URL - but attribute routes go in the route table + // first. + [Fact] + public async Task AttributeRoutedAction_TriedBeforeConventionalRouting() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Home/About"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Home/About", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("About", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithActionParameter_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Blog/Edit/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Blog/Edit/5", result.ExpectedUrls); + Assert.Equal("Blog", result.Controller); + Assert.Equal("Edit", result.Action); + + Assert.Contains( + new KeyValuePair("controller", "Blog"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("action", "Edit"), + result.RouteValues); + + Assert.Contains( + new KeyValuePair("postId", "5"), + result.RouteValues); + } + + // There's no [HttpGet] on the action here. + [Fact] + public async Task AttributeRoutedAction_ControllerLevelRoute_IsReachable() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Employee"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/api/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + } + + // We are intentionally skipping GET because we have another method with [HttpGet] on the same controller + // and a test that verifies that if you define another action with a specific verb we'll route to that + // more specific action. + [Theory] + [InlineData("PUT")] + [InlineData("POST")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnAction_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Store/Shop/Orders"); + + // 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("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("Orders", result.Action); + } + + [Theory] + [InlineData("GET")] + [InlineData("POST")] + [InlineData("PUT")] + [InlineData("PATCH")] + [InlineData("DELETE")] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndController_IsReachable(string method) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Employee/5/Salary"); + + // 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/Employee/5/Salary", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("Salary", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_RouteAttributeOnActionAndHttpGetOnDifferentAction_ReachesHttpGetAction() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Get, "http://localhost/Store/Shop/Orders"); + + // 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("/Store/Shop/Orders", result.ExpectedUrls); + Assert.Equal("Store", result.Controller); + Assert.Equal("GetOrders", result.Action); + } + + // There's no [HttpGet] on the action here. + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbs_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee"); + + // 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/Employee", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateEmployee", result.Action); + } + + [Theory] + [InlineData("PUT")] + [InlineData("PATCH")] + public async Task AttributeRoutedAction_ControllerLevelRoute_WithAcceptVerbsAndRouteTemplate_IsReachable(string verb) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/Manager"); + + // 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/Employee/Manager", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("UpdateManager", result.Action); + } + + [Theory] + [InlineData("PUT", "Bank")] + [InlineData("PATCH", "Bank")] + [InlineData("PUT", "Bank/Update")] + [InlineData("PATCH", "Bank/Update")] + public virtual async Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) + { + // Arrange + var expectedUrl = "/Bank/Update"; + var message = new HttpRequestMessage(new HttpMethod(verb), "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(new string[] { expectedUrl, expectedUrl }, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal("UpdateBank", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_WithCustomHttpAttributes_IsReachable() + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod("MERGE"), "http://localhost/api/Employee/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.Contains("/api/Employee/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("MergeEmployee", result.Action); + } + + // There's an [HttpGet] with its own template on the action here. + [Theory] + [InlineData("GET", "GetAdministrator")] + [InlineData("DELETE", "DeleteAdministrator")] + public async Task AttributeRoutedAction_ControllerLevelRoute_CombinedWithActionRoute_IsReachable(string verb, string action) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(verb), "http://localhost/api/Employee/5/Administrator"); + + // 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/Employee/5/Administrator", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal(action, result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_ActionLevelRouteWithTildeSlash_OverridesControllerLevelRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Manager/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Manager/5", result.ExpectedUrls); + Assert.Equal("Employee", result.Controller); + Assert.Equal("GetManager", result.Action); + + Assert.Contains( + new KeyValuePair("id", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Team/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Team/5", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganization", result.Action); + + Assert.Contains( + new KeyValuePair("teamId", "5"), + result.RouteValues); + } + + [Fact] + public async Task AttributeRoutedAction_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Teams"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/Teams", result.ExpectedUrls); + Assert.Equal("Team", result.Controller); + Assert.Equal("GetOrganizations", result.Action); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Organization/5"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Club/5", response); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() + { + // Arrange & Act + var response = await Client.GetStringAsync("http://localhost/Teams/AllTeams"); + + // Assert + Assert.NotNull(response); + Assert.Equal("/Teams/AllOrganizations", response); + } + + [Theory] + [InlineData("", "/TeamName/DefaultName")] + [InlineData("CustomName", "/TeamName/CustomName")] + public virtual async Task AttributeRoutedAction_PreservesDefaultValue_IfRouteValueIsNull(string teamName, string expected) + { + // Arrange & Act + var body = await Client.GetStringAsync("http://localhost/TeamName/" + teamName); + + // Assert + Assert.NotNull(body); + var result = JsonConvert.DeserializeObject(body); + Assert.Single(result.ExpectedUrls, expected); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToSelf() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithAmbientController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Get", id = 5 }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/api/Employee/5", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToAttributeRoutedController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "ShowPosts", controller = "Blog" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/Blog/ShowPosts", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToConventionalController() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee").To(new { action = "Index", controller = "Home" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/", result.Link); + } + + [Theory] + [InlineData("GET", "Get")] + [InlineData("PUT", "Put")] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( + string method, + string actionName) + { + // Arrange + var message = new HttpRequestMessage(new HttpMethod(method), "http://localhost/api/Company/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("Company", result.Controller); + Assert.Equal(actionName, result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("Company", result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + { + // Arrange & Act + var response = await Client.DeleteAsync("http://localhost/api/Company/5"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("Delete", result.Action); + + Assert.Equal("/api/Company/5", result.ExpectedUrls.Single()); + Assert.Equal("RemoveCompany", result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() + { + // Arrange + var url = LinkFrom("http://localhost") + .To(new { id = 5 }); + + // Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Employees"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetEmployees", result.Action); + + Assert.Equal("/api/Company/5/Employees", result.ExpectedUrls.Single()); + Assert.Null(result.RouteName); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/api/Company/5/Departments"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Company", result.Controller); + Assert.Equal("GetDepartments", result.Action); + + Assert.Equal("/api/Company/5/Departments", result.ExpectedUrls.Single()); + Assert.Equal("Departments", result.RouteName); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/") + .To(new { action = "BuyTickets", controller = "Flight", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Home", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "BuyTickets" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight/BuyTickets", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_StaysInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_LinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/api/Employee") + .To(new { action = "Schedule", controller = "Rail", area = "Travel" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Employee", result.Controller); + Assert.Equal("List", result.Action); + + Assert.Equal("/ContosoCorp/Trains/CheckSchedule", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_ImplicitLinkToArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule").To(new { action = "Index" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains/CheckSchedule") + .To(new { action = "Index", controller = "Home", area = "" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Schedule", result.Action); + + Assert.Equal("/", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Index", controller = "Flight", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Flight", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "Index", controller = "Rail", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/ContosoCorp/Trains", result.Link); + } + + [Fact] + public virtual async Task ConventionalRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Fact] + public virtual async Task AttributeRoutedAction_InArea_LinkToAnotherArea() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "ListUsers", controller = "UserManagement", area = "Admin" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Admin/Users/All", result.Link); + } + + [Theory] + [InlineData("/Bank/Deposit", "PUT", "Deposit")] + [InlineData("/Bank/Deposit", "POST", "Deposit")] + [InlineData("/Bank/Deposit/5", "PUT", "Deposit")] + [InlineData("/Bank/Deposit/5", "POST", "Deposit")] + [InlineData("/Bank/Withdraw/5", "POST", "Withdraw")] + public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Banks", result.Controller); + Assert.Equal(actionName, result.Action); + } + + // These verbs don't match + [Theory] + [InlineData("/Bank/Deposit", "GET")] + [InlineData("/Bank/Deposit/5", "DELETE")] + [InlineData("/Bank/Withdraw/5", "GET")] + public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("/Order/Add/1", "GET", "Add")] + [InlineData("/Order/Add", "POST", "Add")] + [InlineData("/Order/Edit/1", "PUT", "Edit")] + [InlineData("/Order/GetOrder", "GET", "GetOrder")] + public async Task AttributeRouting_RouteNameTokenReplace_Reachable(string path, string verb, string actionName) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains(path, result.ExpectedUrls); + Assert.Equal("Order", result.Controller); + Assert.Equal(actionName, result.Action); + } + + private static LinkBuilder LinkFrom(string url) + { + return new LinkBuilder(url); + } + + // See TestResponseGenerator in RoutingWebSite for the code that generates this data. + 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; } + } + + private class LinkBuilder + { + public LinkBuilder(string url) + { + Url = url; + + Values = new Dictionary(); + Values.Add("link", string.Empty); + } + + public string Url { get; set; } + + public Dictionary Values { get; set; } + + public LinkBuilder To(object values) + { + var dictionary = new RouteValueDictionary(values); + foreach (var kvp in dictionary) + { + Values.Add("link_" + kvp.Key, kvp.Value); + } + + return this; + } + + public override string ToString() + { + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); + } + + public static implicit operator string(LinkBuilder builder) + { + return builder.ToString(); + } + } + } +} diff --git a/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs b/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs deleted file mode 100644 index 9d58e35998..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Admin/UserManagementController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Admin -{ - [Area("Admin")] - [Route("[area]/Users")] - public class UserManagementController : Controller - { - private readonly TestResponseGenerator _generator; - - public UserManagementController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("All")] - public IActionResult ListUsers() - { - return _generator.Generate("Admin/Users/All"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs b/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs deleted file mode 100644 index 76e83adf58..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Order/OrderController.cs +++ /dev/null @@ -1,25 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Areas.Order -{ - [Area("Order")] - [Route("Order/[action]", Name = "[area]_[action]")] - public class OrderController : Controller - { - private readonly TestResponseGenerator _generator; - - public OrderController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public IActionResult GetOrder() - { - return _generator.Generate("/Order/GetOrder"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs deleted file mode 100644 index adaf6e46cc..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/FlightController.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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; - -namespace DispatchingWebSite -{ - // This controller is reachable via traditional routing. - [Area("Travel")] - public class FlightController - { - private readonly TestResponseGenerator _generator; - - public FlightController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/Travel/Flight", "/Travel/Flight/Index"); - } - - [HttpPost] - public IActionResult BuyTickets() - { - return _generator.Generate("/Travel/Flight/BuyTickets"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs deleted file mode 100644 index 51b20ea566..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/HomeController.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Travel -{ - [Area("Travel")] - public class HomeController : Controller - { - private readonly TestResponseGenerator _generator; - - public HomeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/Travel", "/Travel/Home", "/Travel/Home/Index"); - } - - [HttpGet("ContosoCorp/AboutTravel")] - public IActionResult About() - { - return _generator.Generate(); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs b/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs deleted file mode 100644 index c12bcd8c6f..0000000000 --- a/test/WebSites/DispatchingWebSite/Areas/Travel/RailController.cs +++ /dev/null @@ -1,30 +0,0 @@ -// 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; - -namespace DispatchingWebSite -{ - [Area("Travel")] - [Route("ContosoCorp/Trains")] - public class RailController - { - private readonly TestResponseGenerator _generator; - - public RailController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/ContosoCorp/Trains"); - } - - [HttpGet("CheckSchedule")] - public IActionResult Schedule() - { - return _generator.Generate("/ContosoCorp/Trains/Schedule"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs b/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs deleted file mode 100644 index 832fb4d8ed..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/BanksController.cs +++ /dev/null @@ -1,52 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - public class BanksController : Controller - { - private readonly TestResponseGenerator _generator; - - public BanksController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("Banks/[action]/{id}")] - [HttpGet("Bank/[action]/{id}")] - public ActionResult Get(int id) - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [AcceptVerbs("PUT", Route = "Bank")] - [HttpPatch("Bank")] - [AcceptVerbs("PUT", Route = "Bank/Update")] - [HttpPatch("Bank/Update")] - public ActionResult UpdateBank() - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [AcceptVerbs("PUT", "POST")] - [Route("Bank/Deposit")] - [Route("Bank/Deposit/{amount}")] - public ActionResult Deposit() - { - return _generator.Generate("/Bank/Deposit", "/Bank/Deposit/5"); - } - - [HttpPost] - [Route("Bank/Withdraw/{id}")] - public ActionResult Withdraw(int id) - { - return _generator.Generate("/Bank/Withdraw/5"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs b/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs deleted file mode 100644 index b48a9c05b4..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/BlogController.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - // This controller contains actions mapped with a single controller-level route. - [Route("Blog/[action]/{postId?}")] - public class BlogController - { - private readonly TestResponseGenerator _generator; - - public BlogController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult ShowPosts() - { - return _generator.Generate("/Blog/ShowPosts"); - } - - public IActionResult Edit(int postId) - { - return _generator.Generate("/Blog/Edit/" + postId); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs b/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs deleted file mode 100644 index cc45b934ef..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/CompanyController.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - // A controller can define a route for all of the actions - // in it and give it a name for link generation purposes. - [Route("api/Company/{id}", Name = "Company")] - public class CompanyController : Controller - { - private readonly TestResponseGenerator _generator; - - public CompanyController(TestResponseGenerator generator) - { - _generator = generator; - } - - // An action with the same template will inherit the name - // from the controller. - [HttpGet] - public ActionResult Get(int id) - { - return _generator.Generate(Url.RouteUrl("Company", new { id = id })); - } - - // Multiple actions can have the same named route as long - // as for a given Name, all the actions have the same template. - // That is, there can't be two link generation entries with same - // name and different templates. - [HttpPut] - public ActionResult Put(int id) - { - return _generator.Generate(Url.RouteUrl("Company", new { id = id })); - } - - // Two actions can have the same template and each of them can have - // a different route name. That is, a given template can have multiple - // names associated with it. - [HttpDelete(Name = "RemoveCompany")] - public ActionResult Delete(int id) - { - return _generator.Generate(Url.RouteUrl("RemoveCompany", new { id = id })); - } - - // An action that defines a non empty template doesn't inherit the name - // from the route on the controller . - [HttpGet("Employees")] - public ActionResult GetEmployees(int id) - { - return _generator.Generate(Url.RouteUrl(new { id = id })); - } - - // An action that defines a non empty template doesn't inherit the name - // from the controller but can perfectly define its own name. - [HttpGet("Departments", Name = "Departments")] - public ActionResult GetDepartments(int id) - { - return _generator.Generate(Url.RouteUrl("Departments", new { id = id })); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs b/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs deleted file mode 100644 index fd946d4752..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/EmployeeController.cs +++ /dev/null @@ -1,73 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - // This controller combines routes on the controller with routes on actions in a REST + navigation property - // style. - [Route("api/Employee")] - public class EmployeeController : Controller - { - private readonly TestResponseGenerator _generator; - - public EmployeeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult List() - { - return _generator.Generate("/api/Employee"); - } - - [AcceptVerbs("PUT", "PATCH")] - public IActionResult UpdateEmployee() - { - return _generator.Generate("/api/Employee"); - } - - [AcceptVerbs("PUT", "PATCH", Route = "Manager")] - public IActionResult UpdateManager() - { - return _generator.Generate("/api/Employee/Manager"); - } - - [HttpMerge("{id}")] - public IActionResult MergeEmployee(int id) - { - return _generator.Generate("/api/Employee/" + id); - } - - [HttpGet("{id}")] - public IActionResult Get(int id) - { - return _generator.Generate("/api/Employee/" + id); - } - - [HttpGet("{id}/Administrator")] - public IActionResult GetAdministrator(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Administrator"); - } - - [HttpGet("~/Manager/{id}")] - public IActionResult GetManager(int id) - { - return _generator.Generate("/Manager/" + id); - } - - [HttpDelete("{id}/Administrator")] - public IActionResult DeleteAdministrator(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Administrator"); - } - - [Route("{id}/Salary")] - public IActionResult Salary(int id) - { - return _generator.Generate("/api/Employee/" + id + "/Salary"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs b/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs deleted file mode 100644 index 25c32c8c3f..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/FriendsController.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - [Route("Friends")] - public class FriendsController : Controller - { - private readonly TestResponseGenerator _generator; - - public FriendsController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - [HttpGet("{id}")] - public IActionResult Get([FromRoute]string id) - { - return _generator.Generate(id == null ? "/Friends" : $"/Friends/{id}"); - } - - [HttpDelete] - public IActionResult Delete() - { - return _generator.Generate("/Friends"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs b/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs deleted file mode 100644 index 0b40b54c15..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/HomeController.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - // This controller is reachable via traditional routing. - public class HomeController : Controller - { - private readonly TestResponseGenerator _generator; - - public HomeController(TestResponseGenerator generator) - { - _generator = generator; - } - - public IActionResult Index() - { - return _generator.Generate("/", "/Home", "/Home/Index"); - } - - public IActionResult About() - { - // There are no urls that reach this action - it's hidden by an attribute route. - return _generator.Generate(); - } - - public IActionResult Contact() - { - return _generator.Generate("/Home/Contact"); - } - - public IActionResult OptionalPath(string path = "default") - { - return _generator.Generate("/Home/OptionalPath/" + path); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs b/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs deleted file mode 100644 index 2b45d3de62..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/MapsController.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - [Route("api/v1/Maps", Name = "v1", Order = 1)] - [Route("api/v2/Maps")] - public class MapsController : Controller - { - private readonly TestResponseGenerator _generator; - - public MapsController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public ActionResult Get() - { - // Multiple attribute routes with name and order. - // We will always generate v2 routes except when - // we explicitly use "v1" to generate a v1 route. - return _generator.Generate( - Url.Action(), - Url.RouteUrl("v1"), - Url.RouteUrl(new { })); - } - - [HttpPost("/api/v2/Maps")] - public ActionResult Post() - { - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - - [HttpPut("{id}")] - [HttpPatch("PartialUpdate/{id}")] - public ActionResult Update(int id) - { - // We will generate "/api/v2/Maps/PartialUpdate/{id}" - // in both cases, v1 routes will be discarded due to their - // Order and for v2 routes PartialUpdate has higher precedence. - // api/v1/Maps/{id} and api/v2/Maps/{id} will only match on PUT. - // api/v1/Maps/PartialUpdate/{id} and api/v2/Maps/PartialUpdate/{id} will only match on PATCH. - return _generator.Generate( - Url.Action(), - Url.RouteUrl(new { })); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs b/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs deleted file mode 100644 index 195d810af6..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/OrderController.cs +++ /dev/null @@ -1,36 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - [Route("Order/[action]/{orderId?}", Name = "Order_[action]")] - public class OrderController : Controller - { - private readonly TestResponseGenerator _generator; - - public OrderController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet] - public IActionResult Add(int orderId) - { - return _generator.Generate("/Order/Add/1"); - } - - [HttpPost] - public IActionResult Add() - { - return _generator.Generate("/Order/Add"); - } - - [HttpPut] - public IActionResult Edit(int orderId) - { - return _generator.Generate("/Order/Edit/1"); - } - } -} diff --git a/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs b/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs deleted file mode 100644 index a6b7e679a5..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/StoreController.cs +++ /dev/null @@ -1,43 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - // This controller contains only actions with individual attribute routes. - public class StoreController : Controller - { - private readonly TestResponseGenerator _generator; - - public StoreController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("Store/Shop/Products")] - public IActionResult ListProducts() - { - return _generator.Generate("/Store/Shop/Products"); - } - - // Intentionally designed to conflict with HomeController#About. - [HttpGet("Home/About")] - public IActionResult About() - { - return _generator.Generate("/Home/About"); - } - - [Route("Store/Shop/Orders")] - public IActionResult Orders() - { - return _generator.Generate("/Store/Shop/Orders"); - } - - [HttpGet("Store/Shop/Orders")] - public IActionResult GetOrders() - { - return _generator.Generate("/Store/Shop/Orders"); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs b/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs deleted file mode 100644 index a5459b8e79..0000000000 --- a/test/WebSites/DispatchingWebSite/Controllers/TeamController.cs +++ /dev/null @@ -1,72 +0,0 @@ -// 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; - -namespace DispatchingWebSite.Controllers -{ - [Route("/Teams", Order = 1)] - public class TeamController : Controller - { - private readonly TestResponseGenerator _generator; - - public TeamController(TestResponseGenerator generator) - { - _generator = generator; - } - - [HttpGet("/Team/{teamId}", Order = 2)] - public ActionResult GetTeam(int teamId) - { - return _generator.Generate("/Team/" + teamId); - } - - [HttpGet("/Team/{teamId}")] - public ActionResult GetOrganization(int teamId) - { - return _generator.Generate("/Team/" + teamId); - } - - [HttpGet("")] - public ActionResult GetTeams() - { - return _generator.Generate("/Teams"); - } - - [HttpGet("", Order = 0)] - public ActionResult GetOrganizations() - { - return _generator.Generate("/Teams"); - } - - [HttpGet("/Club/{clubId?}")] - public ActionResult GetClub() - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("/Organization/{clubId?}", Order = 1)] - public ActionResult GetClub(int clubId) - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("AllTeams")] - public ActionResult GetAllTeams() - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("AllOrganizations", Order = 0)] - public ActionResult GetAllTeams(int notRelevant) - { - return Content(Url.Action(), "text/plain"); - } - - [HttpGet("/TeamName/{*Name=DefaultName}/")] - public ActionResult GetTeam(string name) - { - return _generator.Generate("/TeamName/" + name); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj b/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj deleted file mode 100644 index df863f0a9c..0000000000 --- a/test/WebSites/DispatchingWebSite/DispatchingWebSite.csproj +++ /dev/null @@ -1,15 +0,0 @@ - - - - $(StandardTestWebsiteTfms) - - - - - - - - - - - diff --git a/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs b/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs deleted file mode 100644 index 19477e4889..0000000000 --- a/test/WebSites/DispatchingWebSite/HttpMergeAttribute.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace DispatchingWebSite -{ - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] - public class HttpMergeAttribute : Attribute, IActionHttpMethodProvider, IRouteTemplateProvider - { - private static readonly IEnumerable _supportedMethods = new[] { "MERGE" }; - - public HttpMergeAttribute(string template) - { - Template = template; - } - - public IEnumerable HttpMethods - { - get { return _supportedMethods; } - } - - /// - public string Template { get; private set; } - - /// - public int? Order { get; set; } - - /// - public string Name { get; set; } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs b/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs deleted file mode 100644 index 41f9455f66..0000000000 --- a/test/WebSites/DispatchingWebSite/TestResponseGenerator.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace DispatchingWebSite -{ - // Generates a response based on the expected URL and action context - public class TestResponseGenerator - { - private readonly ActionContext _actionContext; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory) - { - _urlHelperFactory = urlHelperFactory; - - _actionContext = contextAccessor.ActionContext; - if (_actionContext == null) - { - throw new InvalidOperationException("ActionContext should not be null here."); - } - } - - public JsonResult Generate(params string[] expectedUrls) - { - var link = (string)null; - var query = _actionContext.HttpContext.Request.Query; - if (query.ContainsKey("link")) - { - var values = query - .Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller") - .ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]); - - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext); - link = urlHelper.Action(query["link_action"], query["link_controller"], values); - } - - var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo; - - return new JsonResult(new - { - expectedUrls = expectedUrls, - actualUrl = _actionContext.HttpContext.Request.Path.Value, - routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name, - routeValues = new Dictionary(_actionContext.RouteData.Values), - - action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName, - controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName, - - link, - }); - } - } -} \ No newline at end of file diff --git a/test/WebSites/DispatchingWebSite/readme.md b/test/WebSites/DispatchingWebSite/readme.md deleted file mode 100644 index 357e09a324..0000000000 --- a/test/WebSites/DispatchingWebSite/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -DispatchingWebSite -=== - -This web site illustrates how to use conventional and attribute dispatching. diff --git a/test/WebSites/RoutingWebSite/Program.cs b/test/WebSites/RoutingWebSite/Program.cs new file mode 100644 index 0000000000..83b2991269 --- /dev/null +++ b/test/WebSites/RoutingWebSite/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 RoutingWebSite +{ + 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/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 2a9f4482dd..c7b905bffb 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -42,21 +42,6 @@ namespace RoutingWebSite "{controller}/{action}/{path?}"); }); } - - 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/DispatchingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs similarity index 67% rename from test/WebSites/DispatchingWebSite/Startup.cs rename to test/WebSites/RoutingWebSite/StartupWithDispatching.cs index f576ad29d3..75737e859f 100644 --- a/test/WebSites/DispatchingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs @@ -14,9 +14,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Logging; -namespace DispatchingWebSite +namespace RoutingWebSite { - public class Startup + public class StartupWithDispatching { // Set up application services public void ConfigureServices(IServiceCollection services) @@ -33,46 +33,26 @@ namespace DispatchingWebSite { app.UseDispatcher(); - //app.UseMvc(routes => + app.UseEndpoint(); + + //app.UseMvcWithEndpoint(routes => //{ - // routes.MapAreaRoute( + // routes.MapAreaEndpoint( // "flightRoute", // "adminRoute", // "{area:exists}/{controller}/{action}", // new { controller = "Home", action = "Index" }, // new { area = "Travel" }); - // routes.MapRoute( + // routes.MapEndpoint( // "ActionAsMethod", // "{controller}/{action}", // defaults: new { controller = "Home", action = "Index" }); - // routes.MapRoute( + // routes.MapEndpoint( // "RouteWithOptionalSegment", // "{controller}/{action}/{path?}"); //}); - - app.UseEndpoint(); } - - public static void Main(string[] args) - { - var host = CreateWebHostBuilder(args) - .ConfigureLogging((hostingContext, logging) => - { - logging.AddConsole(); - }) - .Build(); - - host.Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - new WebHostBuilder() - .UseContentRoot(Directory.GetCurrentDirectory()) - .UseStartup() - .UseKestrel() - .UseIISIntegration(); } -} - +} \ No newline at end of file From 3547341762b34f272af7da6cb22e09c450c79bf6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 20 Jun 2018 09:02:52 +1200 Subject: [PATCH 063/316] Add support for conventional routes with dispatching (#7928) --- .../MvcApplicationBuilderExtensions.cs | 58 +++- .../Builder/MvcEndpointInfo.cs | 105 +++++++ .../Builder/MvcEndpointInfoBuilder.cs | 19 ++ .../MvcEndpointInfoBuilderExtensions.cs | 229 ++++++++++++++ .../Internal/MvcEndpointDataSource.cs | 273 ++++++++++++++--- .../Internal/RouteTemplateWriter.cs | 56 ++++ .../MvcApplicationBuilderExtensionsTest.cs | 20 ++ .../MvcEndpointInfoBuilderExtensionsTest.cs | 282 ++++++++++++++++++ .../Internal/MvcEndpointDataSourceTests.cs | 241 ++++++++++++++- .../Internal/RouteTemplateWriterTests.cs | 33 ++ .../DispatchingTests.cs | 220 +------------- .../RoutingWebSite/StartupWithDispatching.cs | 34 +-- 12 files changed, 1264 insertions(+), 306 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 2dcbcd8fe4..8e0ed3bd2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Linq; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; @@ -75,15 +77,7 @@ namespace Microsoft.AspNetCore.Builder throw new ArgumentNullException(nameof(configureRoutes)); } - // Verify if AddMvc was done before calling UseMvc - // We use the MvcMarkerService to make sure if all the services were added. - if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null) - { - throw new InvalidOperationException(Resources.FormatUnableToFindServices( - nameof(IServiceCollection), - "AddMvc", - "ConfigureServices(...)")); - } + VerifyMvcIsRegistered(app); var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService(); middlewarePipelineBuilder.ApplicationBuilder = app.New(); @@ -99,5 +93,51 @@ namespace Microsoft.AspNetCore.Builder return app.UseRouter(routes.Build()); } + + public static IApplicationBuilder UseMvcWithEndpoint( + this IApplicationBuilder app, + Action configureRoutes) + { + if (app == null) + { + throw new ArgumentNullException(nameof(app)); + } + + if (configureRoutes == null) + { + throw new ArgumentNullException(nameof(configureRoutes)); + } + + VerifyMvcIsRegistered(app); + + var mvcEndpointDataSource = app.ApplicationServices + .GetRequiredService>() + .OfType() + .First(); + + var constraintResolver = app.ApplicationServices.GetRequiredService(); + + MvcEndpointInfoBuilder routeBuilder = new MvcEndpointInfoBuilder(constraintResolver); + + configureRoutes(routeBuilder); + + mvcEndpointDataSource.ConventionalEndpointInfos.AddRange(routeBuilder.EndpointInfos); + mvcEndpointDataSource.InitializeEndpoints(); + + return app.UseEndpoint(); + } + + private static void VerifyMvcIsRegistered(IApplicationBuilder app) + { + // Verify if AddMvc was done before calling UseMvc + // We use the MvcMarkerService to make sure if all the services were added. + if (app.ApplicationServices.GetService(typeof(MvcMarkerService)) == null) + { + throw new InvalidOperationException(Resources.FormatUnableToFindServices( + nameof(IServiceCollection), + "AddMvc", + "ConfigureServices(...)")); + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs new file mode 100644 index 0000000000..f88f6e4ded --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -0,0 +1,105 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Globalization; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Template; + +namespace Microsoft.AspNetCore.Builder +{ + public class MvcEndpointInfo + { + public MvcEndpointInfo( + string name, + string template, + RouteValueDictionary defaults, + IDictionary constraints, + RouteValueDictionary dataTokens, + IInlineConstraintResolver constraintResolver) + { + Name = name; + Template = template ?? string.Empty; + DataTokens = dataTokens; + + try + { + // Data we parse from the template will be used to fill in the rest of the constraints or + // defaults. The parser will throw for invalid routes. + ParsedTemplate = TemplateParser.Parse(template); + + Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints); + Defaults = GetDefaults(ParsedTemplate, defaults); + } + catch (Exception exception) + { + throw new RouteCreationException( + string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and template '{1}'.", name, template), exception); + } + } + + public string Name { get; } + public string Template { get; } + public RouteValueDictionary Defaults { get; } + public IDictionary Constraints { get; } + public RouteValueDictionary DataTokens { get; } + internal RouteTemplate ParsedTemplate { get; private set; } + + private static IDictionary GetConstraints( + IInlineConstraintResolver inlineConstraintResolver, + RouteTemplate parsedTemplate, + IDictionary constraints) + { + var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText); + + if (constraints != null) + { + foreach (var kvp in constraints) + { + constraintBuilder.AddConstraint(kvp.Key, kvp.Value); + } + } + + foreach (var parameter in parsedTemplate.Parameters) + { + if (parameter.IsOptional) + { + constraintBuilder.SetOptional(parameter.Name); + } + + foreach (var inlineConstraint in parameter.InlineConstraints) + { + constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint); + } + } + + return constraintBuilder.Build(); + } + + private static RouteValueDictionary GetDefaults( + RouteTemplate parsedTemplate, + RouteValueDictionary defaults) + { + var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults); + + foreach (var parameter in parsedTemplate.Parameters) + { + if (parameter.DefaultValue != null) + { + if (result.ContainsKey(parameter.Name)) + { + throw new InvalidOperationException( + string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + } + else + { + result.Add(parameter.Name, parameter.DefaultValue); + } + } + } + + return result; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs new file mode 100644 index 0000000000..99000fdd08 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs @@ -0,0 +1,19 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Builder +{ + public class MvcEndpointInfoBuilder + { + public MvcEndpointInfoBuilder(IInlineConstraintResolver constraintResolver) + { + ConstraintResolver = constraintResolver; + } + + public List EndpointInfos { get; } = new List(); + public IInlineConstraintResolver ConstraintResolver { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs new file mode 100644 index 0000000000..b52ef3692b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Constraints; + +namespace Microsoft.AspNetCore.Builder +{ + /// + /// Provides extension methods for to add endpoints. + /// + public static class MvcEndpointInfoBuilderExtensions + { + #region MapEndpoint + /// + /// Adds a endpoint to the with the specified name and template. + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The URL pattern of the endpoint. + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template) + { + endpointBuilder.MapEndpoint(name, template, null); + return endpointBuilder; + } + + /// + /// Adds a endpoint to the with the specified name, template, and default values. + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the names + /// and values of the default values. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults) + { + return endpointBuilder.MapEndpoint(name, template, defaults, null); + } + + /// + /// Adds a endpoint to the with the specified name, template, default values, and + /// constraints. + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the names + /// and values of the default values. + /// + /// + /// An object that contains constraints for the endpoint. The object's properties represent the names and values + /// of the constraints. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints) + { + return endpointBuilder.MapEndpoint(name, template, defaults, constraints, null); + } + + /// + /// Adds a endpoint to the with the specified name, template, default values, and + /// data tokens. + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the names + /// and values of the default values. + /// + /// + /// An object that contains constraints for the endpoint. The object's properties represent the names and values + /// of the constraints. + /// + /// + /// An object that contains data tokens for the endpoint. The object's properties represent the names and values + /// of the data tokens. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints, object dataTokens) + { + endpointBuilder.EndpointInfos.Add(new MvcEndpointInfo( + name, + template, + new RouteValueDictionary(defaults), + new RouteValueDictionary(constraints), + new RouteValueDictionary(dataTokens), + endpointBuilder.ConstraintResolver)); + + return endpointBuilder; + } + #endregion + + #region MapAreaEndpoint + /// + /// Adds a endpoint to the with the given MVC area with the specified + /// , and . + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The MVC area name. + /// The URL pattern of the endpoint. + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapAreaEndpoint( + this MvcEndpointInfoBuilder endpointBuilder, + string name, + string areaName, + string template) + { + MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults: null, constraints: null, dataTokens: null); + return endpointBuilder; + } + + /// + /// Adds a endpoint to the with the given MVC area with the specified + /// , , , and + /// . + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The MVC area name. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the + /// names and values of the default values. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapAreaEndpoint( + this MvcEndpointInfoBuilder endpointBuilder, + string name, + string areaName, + string template, + object defaults) + { + MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints: null, dataTokens: null); + return endpointBuilder; + } + + /// + /// Adds a endpoint to the with the given MVC area with the specified + /// , , , + /// , and . + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The MVC area name. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the + /// names and values of the default values. + /// + /// + /// An object that contains constraints for the endpoint. The object's properties represent the names and + /// values of the constraints. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapAreaEndpoint( + this MvcEndpointInfoBuilder endpointBuilder, + string name, + string areaName, + string template, + object defaults, + object constraints) + { + MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints, dataTokens: null); + return endpointBuilder; + } + + /// + /// Adds a endpoint to the with the given MVC area with the specified + /// , , , + /// , , and . + /// + /// The to add the endpoint to. + /// The name of the endpoint. + /// The MVC area name. + /// The URL pattern of the endpoint. + /// + /// An object that contains default values for endpoint parameters. The object's properties represent the + /// names and values of the default values. + /// + /// + /// An object that contains constraints for the endpoint. The object's properties represent the names and + /// values of the constraints. + /// + /// + /// An object that contains data tokens for the endpoint. The object's properties represent the names and + /// values of the data tokens. + /// + /// A reference to this instance after the operation has completed. + public static MvcEndpointInfoBuilder MapAreaEndpoint( + this MvcEndpointInfoBuilder endpointBuilder, + string name, + string areaName, + string template, + object defaults, + object constraints, + object dataTokens) + { + if (endpointBuilder == null) + { + throw new ArgumentNullException(nameof(endpointBuilder)); + } + + if (string.IsNullOrEmpty(areaName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); + } + + var defaultsDictionary = new RouteValueDictionary(defaults); + defaultsDictionary["area"] = defaultsDictionary["area"] ?? areaName; + + var constraintsDictionary = new RouteValueDictionary(constraints); + constraintsDictionary["area"] = constraintsDictionary["area"] ?? new StringRouteConstraint(areaName); + + endpointBuilder.MapEndpoint(name, template, defaultsDictionary, constraintsDictionary, dataTokens); + return endpointBuilder; + } + #endregion + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index f91863b862..7bce3996a5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -4,11 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Internal @@ -17,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; + private readonly IServiceProvider _serviceProvider; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; private readonly List _endpoints; @@ -25,7 +30,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, MvcEndpointInvokerFactory invokerFactory, - IEnumerable actionDescriptorChangeProviders) + IEnumerable actionDescriptorChangeProviders, + IServiceProvider serviceProvider) { if (actions == null) { @@ -42,70 +48,247 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(actionDescriptorChangeProviders)); } + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + _actions = actions; _invokerFactory = invokerFactory; + _serviceProvider = serviceProvider; _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); _endpoints = new List(); - - InitializeEndpoints(); + ConventionalEndpointInfos = new List(); } - private void InitializeEndpoints() + public void InitializeEndpoints() { - // note: this code has haxxx. This will only work in some constrained scenarios foreach (var action in _actions.ActionDescriptors.Items) { if (action.AttributeRouteInfo == null) { - // Action does not have an attribute route - continue; - } - - RequestDelegate invokerDelegate = (context) => - { - var values = context.Features.Get().Values; - var routeData = new RouteData(); - foreach (var kvp in values) + // Check each of the conventional templates to see if the action would be reachable + // If the action and template are compatible then create an endpoint with the + // area/controller/action parameter parts replaced with literals + // + // e.g. {controller}/{action} with HomeController.Index and HomeController.Login + // would result in endpoints: + // - Home/Index + // - Home/Login + foreach (var endpointInfo in ConventionalEndpointInfos) { - routeData.Values.Add(kvp.Key, kvp.Value); - } + var actionRouteValues = action.RouteValues; + var endpointTemplateSegments = endpointInfo.ParsedTemplate.Segments; - var actionContext = new ActionContext(context, routeData, action); - - var invoker = _invokerFactory.CreateInvoker(actionContext); - return invoker.InvokeAsync(); - }; - - var metadata = new List(); - - // Add filter descriptors to endpoint metadata - metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter)); - - if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) - { - foreach (var actionConstraint in action.ActionConstraints) - { - if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint) + if (MatchRouteValue(action, endpointInfo, "Area") + && MatchRouteValue(action, endpointInfo, "Controller") + && MatchRouteValue(action, endpointInfo, "Action")) { - metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods)); + var newEndpointTemplate = TemplateParser.Parse(endpointInfo.Template); + + for (var i = 0; i < newEndpointTemplate.Segments.Count; i++) + { + // Check if the template can be shortened because the remaining parameters are optional + // + // e.g. Matching template {controller=Home}/{action=Index}/{id?} against HomeController.Index + // can resolve to the following endpoints: + // - /Home/Index/{id?} + // - /Home + // - / + if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newEndpointTemplate)) + { + var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i)); + + var subEndpoint = CreateEndpoint(action, subTemplate, 0, endpointInfo); + _endpoints.Add(subEndpoint); + } + + var segment = newEndpointTemplate.Segments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + + if (part.IsParameter && IsMvcParameter(part.Name)) + { + // Replace parameter with literal value + segment.Parts[j] = TemplatePart.CreateLiteral(action.RouteValues[part.Name]); + } + } + } + + var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments); + + var endpoint = CreateEndpoint(action, newTemplate, 0, endpointInfo); + _endpoints.Add(endpoint); } } } - - var metadataCollection = new EndpointMetadataCollection(metadata); - - _endpoints.Add(new MatcherEndpoint( - next => invokerDelegate, - action.AttributeRouteInfo.Template, - action.RouteValues, - action.AttributeRouteInfo.Order, - metadataCollection, - action.DisplayName, - new Address(action.AttributeRouteInfo.Name))); + else + { + var endpoint = CreateEndpoint(action, action.AttributeRouteInfo.Template, action.AttributeRouteInfo.Order, action.AttributeRouteInfo); + _endpoints.Add(endpoint); + } } } + private bool IsMvcParameter(string name) + { + if (string.Equals(name, "Area", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "Controller", StringComparison.OrdinalIgnoreCase) + || string.Equals(name, "Action", StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; + } + + private bool UseDefaultValuePlusRemainingSegementsOptional(int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, RouteTemplate template) + { + // Check whether the remaining segments are all optional and one or more of them is + // for area/controller/action and has a default value + var usedDefaultValue = false; + + for (var i = segmentIndex; i < template.Segments.Count; i++) + { + var segment = template.Segments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll) + { + continue; + } + if (part.IsParameter) + { + if (IsMvcParameter(part.Name)) + { + if (endpointInfo.Defaults[part.Name] is string defaultValue + && action.RouteValues.TryGetValue(part.Name, out var routeValue) + && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) + { + usedDefaultValue = true; + continue; + } + } + } + + // Stop because there is a non-optional/non-defaulted trailing value + return false; + } + } + + return usedDefaultValue; + } + + private bool MatchRouteValue(ActionDescriptor action, MvcEndpointInfo endpointInfo, string routeKey) + { + if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue)) + { + // Action does not have a value for this routeKey, most likely because action is not in an area + // Check that the template does not have a parameter for the routeKey + var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); + if (matchingParameter == null) + { + return true; + } + } + else + { + if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); + if (matchingParameter != null) + { + // Check that the value matches against constraints on that parameter + // e.g. For {controller:regex((Home|Login))} the controller value must match the regex + // + // REVIEW: This is really ugly + if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) + && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + { + // Did not match constraint + return false; + } + + return true; + } + } + + return false; + } + + private class DummyRouter : IRouter + { + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + return null; + } + + public Task RouteAsync(RouteContext context) + { + return Task.CompletedTask; + } + } + + private MatcherEndpoint CreateEndpoint(ActionDescriptor action, string template, int order, object source) + { + RequestDelegate invokerDelegate = (context) => + { + var values = context.Features.Get().Values; + var routeData = new RouteData(); + foreach (var kvp in values) + { + if (kvp.Value != null) + { + routeData.Values.Add(kvp.Key, kvp.Value); + } + } + + var actionContext = new ActionContext(context, routeData, action); + + var invoker = _invokerFactory.CreateInvoker(actionContext); + return invoker.InvokeAsync(); + }; + + var metadata = new List(); + // REVIEW: Used for debugging. Consider removing before release + metadata.Add(source); + metadata.Add(action); + + // Add filter descriptors to endpoint metadata + if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0) + { + metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter)); + } + + if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) + { + foreach (var actionConstraint in action.ActionConstraints) + { + if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint) + { + metadata.Add(new HttpMethodEndpointConstraint(httpMethodActionConstraint.HttpMethods)); + } + } + } + + var metadataCollection = new EndpointMetadataCollection(metadata); + var endpoint = new MatcherEndpoint( + next => invokerDelegate, + template, + action.RouteValues, + order, + metadataCollection, + action.DisplayName, + address: null); + return endpoint; + } + private IChangeToken GetCompositeChangeToken() { if (_actionDescriptorChangeProviders.Length == 1) @@ -136,5 +319,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } public override IReadOnlyList Endpoints => _endpoints; + + public List ConventionalEndpointInfos { get; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs new file mode 100644 index 0000000000..b17df21be2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs @@ -0,0 +1,56 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Routing.Template; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal static class RouteTemplateWriter + { + public static string ToString(IEnumerable routeSegments) + { + return string.Join("/", routeSegments.Select(s => ToString(s))); + } + + private static string ToString(TemplateSegment templateSegment) + { + return string.Join(string.Empty, templateSegment.Parts.Select(p => ToString(p))); + } + + private static string ToString(TemplatePart templatePart) + { + if (templatePart.IsParameter) + { + var partText = "{"; + if (templatePart.IsCatchAll) + { + partText += "*"; + } + partText += templatePart.Name; + foreach (var item in templatePart.InlineConstraints) + { + partText += ":"; + partText += item.Constraint; + } + if (templatePart.DefaultValue != null) + { + partText += "="; + partText += templatePart.DefaultValue; + } + if (templatePart.IsOptional) + { + partText += "?"; + } + partText += "}"; + + return partText; + } + else + { + return templatePart.Text; + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index 8470b7ea25..3b2f75f341 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -29,5 +29,25 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder "in the application startup code.", exception.Message); } + + [Fact] + public void UseMvcWithEndpoint_ThrowsInvalidOperationException_IfMvcMarkerServiceIsNotRegistered() + { + // Arrange + var applicationBuilderMock = new Mock(); + applicationBuilderMock + .Setup(s => s.ApplicationServices) + .Returns(Mock.Of()); + + // Act & Assert + var exception = Assert.Throws( + () => applicationBuilderMock.Object.UseMvcWithEndpoint(rb => { })); + + Assert.Equal( + "Unable to find the required services. Please add all the required services by calling " + + "'IServiceCollection.AddMvc' inside the call to 'ConfigureServices(...)' " + + "in the application startup code.", + exception.Message); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs new file mode 100644 index 0000000000..4a1431793b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs @@ -0,0 +1,282 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Constraints; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Core.Test.Builder +{ + public class MvcEndpointInfoBuilderExtensionsTest + { + #region MapAreaEndpoint + [Fact] + public void MapAreaEndpoint_Simple() + { + // Arrange + var builder = CreateEndpointBuilder(); + + // Act + builder.MapAreaEndpoint(name: null, areaName: "admin", template: "site/Admin/"); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Null(endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + }); + Assert.Empty(endpointInfo.DataTokens); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("admin", kvp.Value); + }); + } + + [Fact] + public void MapAreaEndpoint_Defaults() + { + // Arrange + var builder = CreateEndpointBuilder(); + + // Act + builder.MapAreaEndpoint( + name: "admin_area", + areaName: "admin", + template: "site/Admin/", + defaults: new { action = "Home" }); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Equal("admin_area", endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + }); + Assert.Empty(endpointInfo.DataTokens); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Home", kvp.Value); + }, + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("admin", kvp.Value); + }); + } + + [Fact] + public void MapAreaEndpoint_DefaultsAndConstraints() + { + // Arrange + var builder = CreateEndpointBuilder(); + + // Act + builder.MapAreaEndpoint( + name: "admin_area", + areaName: "admin", + template: "site/Admin/", + defaults: new { action = "Home" }, + constraints: new { id = new IntRouteConstraint() }); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Equal("admin_area", endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + }, + kvp => + { + Assert.Equal("id", kvp.Key); + Assert.IsType(kvp.Value); + }); + Assert.Empty(endpointInfo.DataTokens); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Home", kvp.Value); + }, + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("admin", kvp.Value); + }); + } + + [Fact] + public void MapAreaEndpoint_DefaultsConstraintsAndDataTokens() + { + // Arrange + var builder = CreateEndpointBuilder(); + + // Act + builder.MapAreaEndpoint( + name: "admin_area", + areaName: "admin", + template: "site/Admin/", + defaults: new { action = "Home" }, + constraints: new { id = new IntRouteConstraint() }, + dataTokens: new { some_token = "hello" }); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Equal("admin_area", endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + }, + kvp => + { + Assert.Equal("id", kvp.Key); + Assert.IsType(kvp.Value); + }); + Assert.Collection( + endpointInfo.DataTokens.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("some_token", kvp.Key); + Assert.Equal("hello", kvp.Value); + }); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("action", kvp.Key); + Assert.Equal("Home", kvp.Value); + }, + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("admin", kvp.Value); + }); + } + + [Fact] + public void MapAreaEndpoint_DoesNotReplaceValuesForAreaIfAlreadyPresentInConstraintsOrDefaults() + { + // Arrange + var builder = CreateEndpointBuilder(); + + // Act + builder.MapAreaEndpoint( + name: "admin_area", + areaName: "admin", + template: "site/Admin/", + defaults: new { area = "Home" }, + constraints: new { area = new IntRouteConstraint() }, + dataTokens: new { some_token = "hello" }); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Equal("admin_area", endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + }); + Assert.Collection( + endpointInfo.DataTokens.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("some_token", kvp.Key); + Assert.Equal("hello", kvp.Value); + }); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("Home", kvp.Value); + }); + } + + [Fact] + public void MapAreaEndpoint_UsesPassedInAreaNameAsIs() + { + // Arrange + var builder = CreateEndpointBuilder(); + var areaName = "user.admin"; + + // Act + builder.MapAreaEndpoint(name: null, areaName: areaName, template: "site/Admin/"); + + // Assert + var endpointInfo = Assert.Single(builder.EndpointInfos); + + Assert.Null(endpointInfo.Name); + Assert.Equal("site/Admin/", endpointInfo.Template); + Assert.Collection( + endpointInfo.Constraints.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.IsType(kvp.Value); + + var values = new RouteValueDictionary(new { area = areaName }); + var match = kvp.Value.Match( + new DefaultHttpContext(), + route: new Mock().Object, + routeKey: kvp.Key, + values: values, + routeDirection: RouteDirection.UrlGeneration); + + Assert.True(match); + }); + Assert.Empty(endpointInfo.DataTokens); + Assert.Collection( + endpointInfo.Defaults.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal(kvp.Value, areaName); + }); + } + #endregion + + private MvcEndpointInfoBuilder CreateEndpointBuilder() + { + var builder = new MvcEndpointInfoBuilder(Mock.Of()); + return builder; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 75eac34e56..f35cc05871 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -13,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Moq; using Xunit; @@ -54,17 +57,16 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal } }, 0)); + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + // Act - var dataSource = new MvcEndpointDataSource( - mockDescriptorProvider.Object, - new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - Array.Empty()); + dataSource.InitializeEndpoints(); // Assert var endpoint = Assert.Single(dataSource.Endpoints); var matcherEndpoint = Assert.IsType(endpoint); - object endpointValue = matcherEndpoint.Values["Name"]; + var endpointValue = matcherEndpoint.Values["Name"]; Assert.Equal(routeValue, endpointValue); Assert.Equal(displayName, matcherEndpoint.DisplayName); @@ -85,8 +87,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var httpContextMock = new Mock(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); - var mockDescriptorProviderMock = new Mock(); - mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + var descriptorProviderMock = new Mock(); + descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List { new ActionDescriptor { @@ -109,11 +111,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var actionInvokerProviderMock = new Mock(); actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny())).Returns(actionInvokerMock.Object); + var dataSource = CreateMvcEndpointDataSource( + descriptorProviderMock.Object, + new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object)); + // Act - var dataSource = new MvcEndpointDataSource( - mockDescriptorProviderMock.Object, - new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object), - Array.Empty()); + dataSource.InitializeEndpoints(); // Assert var endpoint = Assert.Single(dataSource.Endpoints); @@ -139,8 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var httpContextMock = new Mock(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); - var mockDescriptorProviderMock = new Mock(); - mockDescriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); + var descriptorProviderMock = new Mock(); + descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); var actionInvokerMock = new Mock(); @@ -154,8 +157,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var changeProvider2Mock = new Mock(); changeProvider2Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object); - var dataSource = new MvcEndpointDataSource( - mockDescriptorProviderMock.Object, + var dataSource = CreateMvcEndpointDataSource( + descriptorProviderMock.Object, new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object), new[] { changeProvider1Mock.Object, changeProvider2Mock.Object }); @@ -166,5 +169,211 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var compositeChangeToken = Assert.IsType(changeToken); Assert.Equal(2, compositeChangeToken.ChangeTokens.Count); } + + [Theory] + [InlineData("{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" })] + [InlineData("{controller}/{id?}", new string[] { })] + [InlineData("{action}/{id?}", new string[] { })] + [InlineData("{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" })] + [InlineData("{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" })] + [InlineData("{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" })] + [InlineData("{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" })] + [InlineData("{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" })] + [InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })] + [InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })] + [InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })] + //[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })] + //[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })] + public void InitializeEndpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + { + // Arrange + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + CreateActionDescriptor("TestController", "TestAction") + }, 0)); + + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var inspectors = finalEndpointTemplates + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .ToArray(); + + // Assert + Assert.Collection(dataSource.Endpoints, inspectors); + } + + [Theory] + [InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{controller}/{action}/{id?}", new string[] { })] + [InlineData("{area=TestArea}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] + public void InitializeEndpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + { + // Arrange + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + CreateActionDescriptor("TestController", "TestAction", "TestArea") + }, 0)); + + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var inspectors = finalEndpointTemplates + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .ToArray(); + + // Assert + Assert.Collection(dataSource.Endpoints, inspectors); + } + + [Fact] + public void InitializeEndpoints_SingleAction_WithActionDefault() + { + // Arrange + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + CreateActionDescriptor("TestController", "TestAction") + }, 0)); + + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}", + new RouteValueDictionary(new { action = "TestAction" }))); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Collection(dataSource.Endpoints, + (e) => Assert.Equal("TestController", Assert.IsType(e).Template), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + } + + [Fact] + public void InitializeEndpoints_MultipleActions_WithActionConstraint() + { + // Arrange + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + CreateActionDescriptor("TestController", "TestAction"), + CreateActionDescriptor("TestController", "TestAction1"), + CreateActionDescriptor("TestController", "TestAction2") + }, 0)); + + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}", + constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" }))); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Collection(dataSource.Endpoints, + (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).Template), + (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).Template)); + } + + [Theory] + [InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })] + [InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })] + public void InitializeEndpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates) + { + // Arrange + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List + { + CreateActionDescriptor("TestController1", "TestAction1"), + CreateActionDescriptor("TestController1", "TestAction2"), + CreateActionDescriptor("TestController1", "TestAction3"), + CreateActionDescriptor("TestController2", "TestAction1") + }, 0)); + + var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + endpointInfoRoute)); + + // Act + dataSource.InitializeEndpoints(); + + var inspectors = finalEndpointTemplates + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .ToArray(); + + // Assert + Assert.Collection(dataSource.Endpoints, inspectors); + } + + private MvcEndpointDataSource CreateMvcEndpointDataSource( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, + MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null, + IEnumerable actionDescriptorChangeProviders = null) + { + if (actionDescriptorCollectionProvider == null) + { + var mockDescriptorProvider = new Mock(); + mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); + + actionDescriptorCollectionProvider = mockDescriptorProvider.Object; + } + + var serviceProviderMock = new Mock(); + serviceProviderMock.Setup(m => m.GetService(typeof(IActionDescriptorCollectionProvider))).Returns(actionDescriptorCollectionProvider); + + var dataSource = new MvcEndpointDataSource( + actionDescriptorCollectionProvider, + mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), + actionDescriptorChangeProviders ?? Array.Empty(), + serviceProviderMock.Object); + + return dataSource; + } + + private MvcEndpointInfo CreateEndpointInfo( + string name, + string template, + RouteValueDictionary defaults = null, + IDictionary constraints = null, + RouteValueDictionary dataTokens = null) + { + var routeOptions = new RouteOptions(); + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + routeOptionsSetup.Configure(routeOptions); + + var constraintResolver = new DefaultInlineConstraintResolver(Options.Create(routeOptions)); + return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver); + } + + private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) + { + return new ActionDescriptor + { + RouteValues = + { + ["controller"] = controller, + ["action"] = action, + ["area"] = area + }, + DisplayName = string.Empty, + }; + } } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs new file mode 100644 index 0000000000..68fcb544ac --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs @@ -0,0 +1,33 @@ +// 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.Internal; +using Microsoft.AspNetCore.Routing.Template; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal +{ + public class RouteTemplateWriterTests + { + [Theory] + [InlineData(@"")] + [InlineData(@"Literal")] + [InlineData(@"Literal1/Literal2")] + [InlineData(@"{controller}")] + [InlineData(@"{controller}/{action}")] + [InlineData(@"{controller}/{action}/{param:test(\?)?}")] + [InlineData(@"{param:test(\w,\w)=jsd}")] + [InlineData(@"some/url-{p1:int:test(3)=hello}/{p2=abc}/{p3?}")] + [InlineData(@"{param:test(abc:somevalue):name(test1:differentname=default-value}")] + [InlineData(@"api/Blog/{controller}/{action}/{id?}")] + [InlineData(@"{p1}.{p2}.{p3}")] + public void ToString_TemplateRoundtrips(string template) + { + var routeTemplate = TemplateParser.Parse(template); + + var output = RouteTemplateWriter.ToString(routeTemplate.Segments); + + Assert.Equal(template, output); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs index 2d5dcb2749..3f8f012c57 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -12,223 +12,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedController_ActionIsReachable() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedController_ActionIsReachable_WithDefaults() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedController_NonActionIsNotReachable() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedController_InArea_ActionIsReachable() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() - { - return Task.CompletedTask; - } - - [Theory(Skip = "Conventional routing WIP")] - [InlineData("", "/Home/OptionalPath/default")] - [InlineData("CustomPath", "/Home/OptionalPath/CustomPath")] - public override Task ConventionalRoutedController_WithOptionalSegment(string optionalSegment, string expected) - { - return Task.CompletedTask; - } - - [Theory(Skip = "URL generation WIP")] - [InlineData("http://localhost/api/v1/Maps")] - [InlineData("http://localhost/api/v2/Maps")] - public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithNameAndOrder(string url) - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_MultipleRouteAttributes_WorksWithOverrideRoutes() - { - return Task.CompletedTask; - } - - [Theory(Skip = "URL generation WIP")] - [InlineData("http://localhost/api/v1/Maps/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/5", "PUT")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PATCH")] - public override Task AttributeRoutedAction_MultipleRouteAttributes_CombinesWithMultipleHttpAttributes( - string url, - string method) - { - return Task.CompletedTask; - } - - [Theory(Skip = "URL generation WIP")] - [InlineData("http://localhost/Banks/Get/5")] - [InlineData("http://localhost/Bank/Get/5")] - public override Task AttributeRoutedAction_MultipleHttpAttributesAndTokenReplacement(string url) - { - return Task.CompletedTask; - } - - [Theory(Skip = "URL generation WIP")] - [InlineData("PUT", "Bank")] - [InlineData("PATCH", "Bank")] - [InlineData("PUT", "Bank/Update")] - [InlineData("PATCH", "Bank/Update")] - public override Task AttributeRoutedAction_AcceptVerbsAndRouteTemplate_IsReachable(string verb, string path) - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkGeneration_OverrideActionOverridesOrderOnController() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkGeneration_OrderOnActionOverridesOrderOnController() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkToSelf() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkWithAmbientController() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkToAttributeRoutedController() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkToConventionalController() - { - return Task.CompletedTask; - } - - [Theory(Skip = "URL generation WIP")] - [InlineData("GET", "Get")] - [InlineData("PUT", "Put")] - public override Task AttributeRoutedAction_LinkWithName_WithNameInheritedFromControllerRoute( - string method, - string actionName) - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_Link_WithNonEmptyActionRouteTemplateAndNoActionRouteName() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkWithName_WithNonEmptyActionRouteTemplateAndActionRouteName() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_LinkToArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_InArea_ImplicitLinkToArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_InArea_StaysInArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_LinkToArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_InArea_ImplicitLinkToArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_InArea_LinkToAttributeRoutedActionInArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "Conventional routing WIP")] - public override Task ConventionalRoutedAction_InArea_LinkToAnotherArea() - { - return Task.CompletedTask; - } - - [Fact(Skip = "URL generation WIP")] - public override Task AttributeRoutedAction_InArea_LinkToAnotherArea() - { - return Task.CompletedTask; - } } -} +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/StartupWithDispatching.cs b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs index 75737e859f..670ec5497d 100644 --- a/test/WebSites/RoutingWebSite/StartupWithDispatching.cs +++ b/test/WebSites/RoutingWebSite/StartupWithDispatching.cs @@ -33,26 +33,24 @@ namespace RoutingWebSite { app.UseDispatcher(); - app.UseEndpoint(); + app.UseMvcWithEndpoint(routes => + { + routes.MapAreaEndpoint( + "flightRoute", + "adminRoute", + "{area:exists}/{controller}/{action}", + new { controller = "Home", action = "Index" }, + new { area = "Travel" }); - //app.UseMvcWithEndpoint(routes => - //{ - // routes.MapAreaEndpoint( - // "flightRoute", - // "adminRoute", - // "{area:exists}/{controller}/{action}", - // new { controller = "Home", action = "Index" }, - // new { area = "Travel" }); + routes.MapEndpoint( + "ActionAsMethod", + "{controller}/{action}", + defaults: new { controller = "Home", action = "Index" }); - // routes.MapEndpoint( - // "ActionAsMethod", - // "{controller}/{action}", - // defaults: new { controller = "Home", action = "Index" }); - - // routes.MapEndpoint( - // "RouteWithOptionalSegment", - // "{controller}/{action}/{path?}"); - //}); + routes.MapEndpoint( + "RouteWithOptionalSegment", + "{controller}/{action}/{path?}"); + }); } } } \ No newline at end of file From 3d32b6da2fb82c58984c495970e6325b2f5f1972 Mon Sep 17 00:00:00 2001 From: Ajay Bhargav Baaskaran Date: Tue, 19 Jun 2018 14:58:40 -0700 Subject: [PATCH 064/316] Upgrade deps and unskip tests (#7936) --- build/dependencies.props | 148 +++++++++--------- .../ViewEngineTests.cs | 4 +- 2 files changed, 76 insertions(+), 76 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 39ff053267..22e161b819 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,91 +5,91 @@ 0.9.9 0.10.13 - 2.2.0-preview1-34481 + 2.2.0-preview1-34492 2.2.0-preview1-17090 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-a-preview1-a-16708 - 2.2.0-a-preview1-a-16708 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-a-preview1-a-16693 - 2.2.0-a-preview1-a-16693 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34481 + 2.2.0-preview1-34492 1.7.0 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-26614-02 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-a-preview1-a-16708 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-26618-02 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 2.0.0 2.1.0 - 2.2.0-preview1-26612-04 - 2.2.0-preview1-34481 - 2.2.0-preview1-34481 + 2.2.0-preview1-26618-02 + 2.2.0-preview1-34492 + 2.2.0-preview1-34492 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26613-07 - 4.6.0-preview1-26613-07 - 4.6.0-preview1-26613-07 + 4.6.0-preview1-26617-01 + 4.6.0-preview1-26617-01 + 4.6.0-preview1-26617-01 0.8.0 2.3.1 2.4.0-beta.1.build3945 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs index eba89d70f7..363f534469 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs @@ -246,11 +246,11 @@ ViewWithNestedLayout-Content Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); } - [Fact(Skip = "https://github.com/aspnet/Mvc/issues/7922")] + [Fact] public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByName() => RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByName"); - [Fact(Skip = "https://github.com/aspnet/Mvc/issues/7922")] + [Fact] public Task RazorViewEngine_RendersViewsFromEmbeddedFileProvider_WhenLookedupByPath() => RazorViewEngine_RendersIndexViewsFromEmbeddedFileProvider("/EmbeddedViews/LookupByPath"); From e7ab81fe0bac76a7f2086c217a97f8d1c4284baa Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 19 Jun 2018 15:00:07 -0700 Subject: [PATCH 065/316] Ensure analyzer package is referenced by Microsoft.AspNetCore.Mvc Fixes #7684 --- .../ActionsMustNotBeAsyncVoidAnalyzer.cs | 2 +- .../ActionsMustNotBeAsyncVoidFixProvider.cs | 2 +- .../ApiActionsAreAttributeRoutedAnalyzer.cs | 2 +- .../ApiActionsAreAttributeRoutedFixProvider.cs | 2 +- ...piActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs | 2 +- ...nsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs | 2 +- .../ApiActionsShouldUseActionResultOfTAnalyzer.cs | 2 +- .../ApiActionsShouldUseActionResultOfTCodeFixProvider.cs | 2 +- .../DiagnosticDescriptors.cs | 2 +- src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj | 2 +- .../ActionsMustNotBeAsyncVoidFacts.cs | 2 +- .../ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs | 2 +- .../ApiActionsAreAttributeRoutedFacts.cs | 2 +- .../ApiActionsShouldUseActionResultOfTFacts.cs | 2 +- .../ApplicationParts/ApplicationAssembliesProviderTest.cs | 1 + 15 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs index 90b2032abb..e3a489a960 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public static readonly string ReturnTypeKey = "ReturnType"; public ActionsMustNotBeAsyncVoidAnalyzer() - : base(DiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid) + : base(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid) { } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs index f36c18d0e6..8adf0c0288 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public class ActionsMustNotBeAsyncVoidFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid.Id); + ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid.Id); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs index 59a8eec020..32b7a4e3ac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers internal const string MethodNameKey = "MethodName"; public ApiActionsAreAttributeRoutedAnalyzer() - : base(DiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted) + : base(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted) { } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs index e97e593a9f..a0ea810b51 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers }; public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted.Id); + ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted.Id); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs index 67eb82bd2b..1e66b2f087 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer : ApiControllerAnalyzerBase { public ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer() - : base(DiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter) + : base(ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter) { } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs index 3637c47217..b238ad8ece 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter.Id); + ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter.Id); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs index ef00b99527..9125d40692 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public static readonly string ReturnTypeKey = "ReturnType"; public ApiActionsShouldUseActionResultOfTAnalyzer() - : base(DiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf) + : base(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf) { } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs index 792b47227b..7dc5b5ec14 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public class ApiActionsShouldUseActionResultOfTCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf.Id); + ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf.Id); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs index 7c8100234c..14f56b5918 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs @@ -5,7 +5,7 @@ using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Analyzers { - public static class DiagnosticDescriptors + public static class ExperimentalDiagnosticDescriptors { public static readonly DiagnosticDescriptor MVC7000_ApiActionsMustBeAttributeRouted = new DiagnosticDescriptor( diff --git a/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj b/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj index 76c448b86b..9e57ee217c 100644 --- a/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj +++ b/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj @@ -9,7 +9,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs index c7432399f1..d3fafb9f5c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ActionsMustNotBeAsyncVoidFacts : AnalyzerTestBase { - private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid; + private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid; protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ActionsMustNotBeAsyncVoidAnalyzer(); diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs index f2954b3047..85c805bca9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsDoNotRequireExplicitModelValidationCheckFacts : AnalyzerTestBase { - private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter; + private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter; protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer(); diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs index 9042db0c0d..d7968d7928 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsAreAttributeRoutedFacts : AnalyzerTestBase { - private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted; + private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted; protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsAreAttributeRoutedAnalyzer(); diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs index ef939ffd3c..1cdd409ddc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiActionsShouldUseActionResultOfTFacts : AnalyzerTestBase { - private static DiagnosticDescriptor DiagnosticDescriptor = DiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf; + private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf; protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } = new ApiActionsShouldUseActionResultOfTAnalyzer(); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs index 9213836156..25f18d8cda 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts // Arrange var excludeAssemblies = new string[] { + "Microsoft.AspNetCore.Mvc.Analyzers", "Microsoft.AspNetCore.Mvc.Test", "Microsoft.AspNetCore.Mvc.Core.TestCommon", }; From 4f7e849cc14e8619706e8f2deecefe8e943a4ef7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 20 Jun 2018 15:22:53 -0700 Subject: [PATCH 066/316] Setting up for ApiConventionAttribute analyzers (#7912) * Setting up for ApiConventionAttribute analyzers --- .../ApiControllerTypeCache.cs | 20 ++ .../CodeAnalysisExtensions.cs | 31 +- .../SymbolApiResponseMetadataProvider.cs | 90 +++++ .../SymbolNames.cs | 8 +- .../AcceptedAtActionResult.cs | 8 +- .../AcceptedAtRouteResult.cs | 8 +- .../AcceptedResult.cs | 12 +- .../BadRequestObjectResult.cs | 8 +- .../BadRequestResult.cs | 6 +- .../ConflictObjectResult.cs | 8 +- .../ConflictResult.cs | 6 +- .../CreatedAtActionResult.cs | 6 +- .../CreatedAtRouteResult.cs | 6 +- .../CreatedResult.cs | 10 +- .../DefaultStatusCodeAttribute.cs | 31 ++ .../NoContentResult.cs | 12 +- .../NotFoundObjectResult.cs | 6 +- .../NotFoundResult.cs | 6 +- .../ObjectResult.cs | 1 - .../OkObjectResult.cs | 6 +- src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs | 6 +- .../ProducesAttribute.cs | 4 +- .../UnauthorizedResult.cs | 6 +- .../UnprocessableEntityObjectResult.cs | 6 +- .../UnprocessableEntityResult.cs | 6 +- .../UnsupportedMediaTypeResult.cs | 6 +- .../CodeAnalysisExtensionsTest.cs | 158 +++++++-- .../MvcFactsTest.cs | 1 - .../SymbolApiResponseMetadataProviderTest.cs | 311 ++++++++++++++++++ ...GetAttributes_OnMethodWithoutAttributes.cs | 7 + .../GetAttributes_WithMethodOverridding.cs | 15 + .../GetAttributes_WithNewMethod.cs | 22 ++ .../GetAttributes_WithoutMethodOverridding.cs | 8 + .../GetResponseMetadataTests.cs | 86 +++++ 34 files changed, 863 insertions(+), 68 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs new file mode 100644 index 0000000000..8f4b2948e9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs @@ -0,0 +1,20 @@ +// 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.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal readonly struct ApiControllerTypeCache + { + public ApiControllerTypeCache(Compilation compilation) + { + ApiConventionAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionAttribute); + ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); + } + + public INamedTypeSymbol ApiConventionAttribute { get; } + + public INamedTypeSymbol ProducesResponseTypeAttribute { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs index 6c191174ab..3fca8e3851 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Analyzers @@ -31,26 +32,27 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } public static bool HasAttribute(this IMethodSymbol methodSymbol, ITypeSymbol attribute, bool inherit) + => GetAttributes(methodSymbol, attribute, inherit).Any(); + + public static IEnumerable GetAttributes(this IMethodSymbol methodSymbol, ITypeSymbol attribute, bool inherit) { Debug.Assert(methodSymbol != null); Debug.Assert(attribute != null); - if (!inherit) - { - return HasAttribute(methodSymbol, attribute); - } - while (methodSymbol != null) { - if (methodSymbol.HasAttribute(attribute)) + foreach (var attributeData in GetAttributes(methodSymbol, attribute)) { - return true; + yield return attributeData; + } + + if (!inherit) + { + break; } methodSymbol = methodSymbol.IsOverride ? methodSymbol.OverriddenMethod : null; } - - return false; } public static bool HasAttribute(this IPropertySymbol propertySymbol, ITypeSymbol attribute, bool inherit) @@ -118,6 +120,17 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } + private static IEnumerable GetAttributes(this ISymbol symbol, ITypeSymbol attribute) + { + foreach (var declaredAttribute in symbol.GetAttributes()) + { + if (attribute.IsAssignableFrom(declaredAttribute.AttributeClass)) + { + yield return declaredAttribute; + } + } + } + private static IEnumerable GetTypeHierarchy(this ITypeSymbol typeSymbol) { while (typeSymbol != null) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs new file mode 100644 index 0000000000..bfc6935572 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -0,0 +1,90 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal class SymbolApiResponseMetadataProvider + { + private const string StatusCodeProperty = "StatusCode"; + private const string StatusCodeConstructorParameter = "statusCode"; + + internal static IList GetResponseMetadata(ApiControllerTypeCache typeCache, IMethodSymbol methodSymbol) + { + var responseMetadataAttributes = methodSymbol.GetAttributes(typeCache.ProducesResponseTypeAttribute, inherit: true); + var metadataItems = new List(); + foreach (var attribute in responseMetadataAttributes) + { + var statusCode = GetStatusCode(attribute); + var metadata = new ApiResponseMetadata(statusCode, attribute, convention: null); + + metadataItems.Add(metadata); + } + + return metadataItems; + } + + internal static int GetStatusCode(AttributeData attribute) + { + const int DefaultStatusCode = 200; + for (var i = 0; i < attribute.NamedArguments.Length; i++) + { + var namedArgument = attribute.NamedArguments[i]; + var namedArgumentValue = namedArgument.Value; + if (string.Equals(namedArgument.Key, StatusCodeProperty, StringComparison.Ordinal) && + namedArgumentValue.Kind == TypedConstantKind.Primitive && + (namedArgumentValue.Type.SpecialType & SpecialType.System_Int32) == SpecialType.System_Int32 && + namedArgumentValue.Value is int statusCode) + { + return statusCode; + } + } + + if (attribute.AttributeConstructor == null) + { + return DefaultStatusCode; + } + + var constructorParameters = attribute.AttributeConstructor.Parameters; + for (var i = 0; i < constructorParameters.Length; i++) + { + var parameter = constructorParameters[i]; + if (string.Equals(parameter.Name, StatusCodeConstructorParameter, StringComparison.Ordinal) && + (parameter.Type.SpecialType & SpecialType.System_Int32) == SpecialType.System_Int32) + { + if (attribute.ConstructorArguments.Length < i) + { + return DefaultStatusCode; + } + + var argument = attribute.ConstructorArguments[i]; + if (argument.Kind == TypedConstantKind.Primitive && argument.Value is int statusCode) + { + return statusCode; + } + } + } + + return DefaultStatusCode; + } + } + + internal readonly struct ApiResponseMetadata + { + public ApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) + { + StatusCode = statusCode; + Attribute = attributeData; + Convention = convention; + } + + public int StatusCode { get; } + + public AttributeData Attribute { get; } + + public IMethodSymbol Convention { get; } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index 9e9065df04..98cd8666be 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; + public const string ApiConventionAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionAttribute"; + public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; public const string IFilterMetadataType = "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata"; @@ -15,12 +17,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; + public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; + public const string PageModelAttributeType = "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageModelAttribute"; public const string PartialMethod = "Partial"; - public const string RenderPartialMethod = "RenderPartial"; + public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; - public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; + public const string RenderPartialMethod = "RenderPartial"; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs index dd0480c39e..291e25930a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs @@ -4,6 +4,7 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -14,8 +15,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that returns a Accepted (202) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class AcceptedAtActionResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status202Accepted; + /// /// Initializes a new instance of the with the values /// provided. @@ -34,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc ActionName = actionName; ControllerName = controllerName; RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); - StatusCode = StatusCodes.Status202Accepted; + StatusCode = DefaultStatusCode; } /// @@ -91,4 +95,4 @@ namespace Microsoft.AspNetCore.Mvc context.HttpContext.Response.Headers[HeaderNames.Location] = url; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs index bfde81d3ae..2fb29b505c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs @@ -8,14 +8,18 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { /// /// An that returns a Accepted (202) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class AcceptedAtRouteResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status202Accepted; + /// /// Initializes a new instance of the class with the values /// provided. @@ -42,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc { RouteName = routeName; RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); - StatusCode = StatusCodes.Status202Accepted; + StatusCode = DefaultStatusCode; } /// @@ -87,4 +91,4 @@ namespace Microsoft.AspNetCore.Mvc context.HttpContext.Response.Headers[HeaderNames.Location] = url; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs index 30b33a4fc5..cf0d383fd3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc @@ -10,8 +11,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that returns an Accepted (202) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class AcceptedResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status202Accepted; + /// /// Initializes a new instance of the class with the values /// provided. @@ -19,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc public AcceptedResult() : base(value: null) { - StatusCode = StatusCodes.Status202Accepted; + StatusCode = DefaultStatusCode; } /// @@ -32,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc : base(value) { Location = location; - StatusCode = StatusCodes.Status202Accepted; + StatusCode = DefaultStatusCode; } /// @@ -59,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc Location = locationUri.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } - StatusCode = StatusCodes.Status202Accepted; + StatusCode = DefaultStatusCode; } /// @@ -83,4 +87,4 @@ namespace Microsoft.AspNetCore.Mvc } } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs index f31552a3a6..9af96ff7cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc @@ -10,8 +11,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that when executed will produce a Bad Request (400) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class BadRequestObjectResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status400BadRequest; + /// /// Creates a new instance. /// @@ -19,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc public BadRequestObjectResult(object error) : base(error) { - StatusCode = StatusCodes.Status400BadRequest; + StatusCode = DefaultStatusCode; } /// @@ -34,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc throw new ArgumentNullException(nameof(modelState)); } - StatusCode = StatusCodes.Status400BadRequest; + StatusCode = DefaultStatusCode; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs index 69025ee49d..5a034b181a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,13 +10,16 @@ namespace Microsoft.AspNetCore.Mvc /// A that when /// executed will produce a Bad Request (400) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class BadRequestResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status400BadRequest; + /// /// Creates a new instance. /// public BadRequestResult() - : base(StatusCodes.Status400BadRequest) + : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs index 29e72e816b..93927d78f9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc @@ -10,8 +11,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that when executed will produce a Conflict (409) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class ConflictObjectResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status409Conflict; + /// /// Creates a new instance. /// @@ -19,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc public ConflictObjectResult(object error) : base(error) { - StatusCode = StatusCodes.Status409Conflict; + StatusCode = DefaultStatusCode; } /// @@ -34,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc throw new ArgumentNullException(nameof(modelState)); } - StatusCode = StatusCodes.Status409Conflict; + StatusCode = DefaultStatusCode; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs index 92eb10385a..a97acb8c31 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs @@ -2,19 +2,23 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { /// /// A that when executed will produce a Conflict (409) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class ConflictResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status409Conflict; + /// /// Creates a new instance. /// public ConflictResult() - : base(StatusCodes.Status409Conflict) + : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs index 6ed312a374..f173c2ba9d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs @@ -8,14 +8,18 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { /// /// An that returns a Created (201) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class CreatedAtActionResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status201Created; + /// /// Initializes a new instance of the with the values /// provided. @@ -34,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc ActionName = actionName; ControllerName = controllerName; RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); - StatusCode = StatusCodes.Status201Created; + StatusCode = DefaultStatusCode; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs index 6e26789563..ad05b6e4a9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs @@ -8,14 +8,18 @@ using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Net.Http.Headers; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { /// /// An that returns a Created (201) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class CreatedAtRouteResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status201Created; + /// /// Initializes a new instance of the class with the values /// provided. @@ -42,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc { RouteName = routeName; RouteValues = routeValues == null ? null : new RouteValueDictionary(routeValues); - StatusCode = StatusCodes.Status201Created; + StatusCode = DefaultStatusCode; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs index 5e1d23a628..1ead848ca2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Mvc @@ -10,8 +11,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that returns a Created (201) response with a Location header. /// + [DefaultStatusCode(DefaultStatusCode)] public class CreatedResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status201Created; + private string _location; /// @@ -29,7 +33,7 @@ namespace Microsoft.AspNetCore.Mvc } Location = location; - StatusCode = StatusCodes.Status201Created; + StatusCode = DefaultStatusCode; } /// @@ -55,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc Location = location.GetComponents(UriComponents.SerializationInfoString, UriFormat.UriEscaped); } - StatusCode = StatusCodes.Status201Created; + StatusCode = DefaultStatusCode; } /// @@ -88,4 +92,4 @@ namespace Microsoft.AspNetCore.Mvc context.HttpContext.Response.Headers[HeaderNames.Location] = Location; } } -} \ No newline at end of file +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs new file mode 100644 index 0000000000..f34ffc4d66 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs @@ -0,0 +1,31 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Specifies the default status code associated with an . + /// + /// + /// This attribute is informational only and does not have any runtime effects. + /// + [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + public sealed class DefaultStatusCodeAttribute : Attribute + { + /// + /// Initializes a new instance of . + /// + /// The default status code. + public DefaultStatusCodeAttribute(int statusCode) + { + StatusCode = statusCode; + } + + /// + /// Gets the default status code. + /// + public int StatusCode { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs index 09a700398e..f4a23075f9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs @@ -2,13 +2,23 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { + /// + /// A that when executed will produce a 204 No Content response. + /// + [DefaultStatusCode(DefaultStatusCode)] public class NoContentResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status204NoContent; + + /// + /// Initializes a new instance. + /// public NoContentResult() - : base(StatusCodes.Status204NoContent) + : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs index 9260784017..c8856473aa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs @@ -2,14 +2,18 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { /// /// An that when executed will produce a Not Found (404) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class NotFoundObjectResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status404NotFound; + /// /// Creates a new instance. /// @@ -17,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc public NotFoundObjectResult(object value) : base(value) { - StatusCode = StatusCodes.Status404NotFound; + StatusCode = DefaultStatusCode; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs index b4b7de1600..ed59417621 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,12 +10,15 @@ namespace Microsoft.AspNetCore.Mvc /// Represents an that when /// executed will produce a Not Found (404) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class NotFoundResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status404NotFound; + /// /// Creates a new instance. /// - public NotFoundResult() : base(StatusCodes.Status404NotFound) + public NotFoundResult() : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs index 1148ceffc1..ded227d22a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs @@ -5,7 +5,6 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc diff --git a/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs index 2990ffc843..3f4162b426 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,8 +10,11 @@ namespace Microsoft.AspNetCore.Mvc /// An that when executed performs content negotiation, formats the entity body, and /// will produce a response if negotiation and formatting succeed. /// + [DefaultStatusCode(DefaultStatusCode)] public class OkObjectResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status200OK; + /// /// Initializes a new instance of the class. /// @@ -18,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc public OkObjectResult(object value) : base(value) { - StatusCode = StatusCodes.Status200OK; + StatusCode = DefaultStatusCode; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs index 49cd649c99..cb6476b72d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,13 +10,16 @@ namespace Microsoft.AspNetCore.Mvc /// An that when executed will produce an empty /// response. /// + [DefaultStatusCode(DefaultStatusCode)] public class OkResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status200OK; + /// /// Initializes a new instance of the class. /// public OkResult() - : base(StatusCodes.Status200OK) + : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs index 202591a1b6..e9346f7330 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs @@ -82,9 +82,7 @@ namespace Microsoft.AspNetCore.Mvc throw new ArgumentNullException(nameof(context)); } - var objectResult = context.Result as ObjectResult; - - if (objectResult != null) + if (context.Result is ObjectResult objectResult) { // Check if there are any IFormatFilter in the pipeline, and if any of them is active. If there is one, // do not override the content type value. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs index a92106ec84..7e3bc7bb7f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,12 +10,15 @@ namespace Microsoft.AspNetCore.Mvc /// Represents an that when /// executed will produce an Unauthorized (401) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class UnauthorizedResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status401Unauthorized; + /// /// Creates a new instance. /// - public UnauthorizedResult() : base(StatusCodes.Status401Unauthorized) + public UnauthorizedResult() : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs index 002d9d97af..85c21a9594 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc @@ -9,8 +10,11 @@ namespace Microsoft.AspNetCore.Mvc /// /// An that when executed will produce a Unprocessable Entity (422) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class UnprocessableEntityObjectResult : ObjectResult { + private const int DefaultStatusCode = StatusCodes.Status422UnprocessableEntity; + /// /// Creates a new instance. /// @@ -27,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc public UnprocessableEntityObjectResult(object error) : base(error) { - StatusCode = StatusCodes.Status422UnprocessableEntity; + StatusCode = DefaultStatusCode; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs index 0851057499..d82cf642a7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,13 +10,16 @@ namespace Microsoft.AspNetCore.Mvc /// A that when /// executed will produce a Unprocessable Entity (422) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class UnprocessableEntityResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status422UnprocessableEntity; + /// /// Creates a new instance. /// public UnprocessableEntityResult() - : base(StatusCodes.Status422UnprocessableEntity) + : base(DefaultStatusCode) { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs index 445ded67d0..175c886874 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc { @@ -9,12 +10,15 @@ namespace Microsoft.AspNetCore.Mvc /// A that when /// executed will produce a UnsupportedMediaType (415) response. /// + [DefaultStatusCode(DefaultStatusCode)] public class UnsupportedMediaTypeResult : StatusCodeResult { + private const int DefaultStatusCode = StatusCodes.Status415UnsupportedMediaType; + /// /// Creates a new instance of . /// - public UnsupportedMediaTypeResult() : base(StatusCodes.Status415UnsupportedMediaType) + public UnsupportedMediaTypeResult() : base(DefaultStatusCode) { } } diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs index e0e9a90d5c..392a16d6bb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs @@ -13,13 +13,123 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class CodeAnalysisExtensionsTest { + private static readonly string Namespace = typeof(CodeAnalysisExtensionsTest).Namespace; + + [Fact] + public async Task GetAttributes_OnMethodWithoutAttributes() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_OnMethodWithoutAttributesClass)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_OnMethodWithoutAttributesClass.Method)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); + + // Assert + Assert.Empty(attributes); + } + + [Fact] + public async Task GetAttributes_OnNonOverriddenMethod_ReturnsAllAttributesOnCurrentAction() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithoutMethodOverridding"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithoutMethodOverridding)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithoutMethodOverridding.Method)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(201, attributeData.ConstructorArguments[0].Value)); + } + + [Fact] + public async Task GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentAction() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithMethodOverridding"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass.Method)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: false); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(400, attributeData.ConstructorArguments[0].Value)); + } + + [Fact] + public async Task GetAttributes_WithInheritTrue_ReturnsAllAttributesOnCurrentActionAndOverridingMethod() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithMethodOverridding"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass.Method)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(400, attributeData.ConstructorArguments[0].Value), + attributeData => Assert.Equal(200, attributeData.ConstructorArguments[0].Value), + attributeData => Assert.Equal(404, attributeData.ConstructorArguments[0].Value)); + } + + [Fact] + public async Task GetAttributes_OnNewMethodOfVirtualBaseMethod() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithNewMethod"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithNewMethodDerived)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithNewMethodDerived.VirtualMethod)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(400, attributeData.ConstructorArguments[0].Value)); + } + + [Fact] + public async Task GetAttributes_OnNewMethodOfNonVirtualBaseMethod() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithNewMethod"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithNewMethodDerived)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithNewMethodDerived.NotVirtualMethod)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(401, attributeData.ConstructorArguments[0].Value)); + } + [Fact] public async Task HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute() { // Arrange var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttributeTest"); + var attribute = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsFalseIfTypeDoesNotHaveAttributeTest"); var testMethod = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); var testProperty = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); @@ -40,7 +150,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers // Arrange var compilation = await GetCompilation(); var attribute = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.ControllerAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.{nameof(HasAttribute_ReturnsTrueIfTypeHasAttribute)}"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(HasAttribute_ReturnsTrueIfTypeHasAttribute)}"); // Act var hasAttribute = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: false); @@ -55,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers // Arrange var compilation = await GetCompilation(); var attribute = compilation.GetTypeByMetadataName("Microsoft.AspNetCore.Mvc.ControllerAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.{nameof(HasAttribute_ReturnsTrueIfBaseTypeHasAttribute)}"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(HasAttribute_ReturnsTrueIfBaseTypeHasAttribute)}"); // Act var hasAttributeWithoutInherit = CodeAnalysisExtensions.HasAttribute(testClass, attribute, inherit: false); @@ -71,9 +181,9 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var @interface = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IHasAttribute_ReturnsTrueForInterfaceContractOnAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeTest"); - var derivedClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeDerived"); + var @interface = compilation.GetTypeByMetadataName($"{Namespace}.IHasAttribute_ReturnsTrueForInterfaceContractOnAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeTest"); + var derivedClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForInterfaceContractOnAttributeDerived"); // Act var hasAttribute = CodeAnalysisExtensions.HasAttribute(testClass, @interface, inherit: true); @@ -89,8 +199,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsTest"); + var attribute = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnMethodsTest"); var method = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); // Act @@ -105,8 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsTest"); + var attribute = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenMethodsTest"); var method = (IMethodSymbol)testClass.GetMembers("SomeMethod").First(); @@ -124,8 +234,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnPropertiesAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnProperties"); + var attribute = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnPropertiesAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnProperties"); var property = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); // Act @@ -140,8 +250,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesAttribute"); - var testClass = compilation.GetTypeByMetadataName($"{GetType().Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenProperties"); + var attribute = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenPropertiesAttribute"); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.HasAttribute_ReturnsTrueForAttributesOnOverriddenProperties"); var property = (IPropertySymbol)testClass.GetMembers("SomeProperty").First(); // Act @@ -158,8 +268,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); - var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesB"); + var source = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); + var target = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsFalseForDifferentTypesB"); // Act var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); @@ -173,7 +283,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(nameof(IsAssignable_ReturnsFalseForDifferentTypes)); - var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); + var source = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsFalseForDifferentTypesA"); var target = compilation.GetTypeByMetadataName($"System.IDisposable"); // Act @@ -188,8 +298,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); - var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); + var source = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); + var target = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfTypesAreExact"); // Act var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); @@ -203,8 +313,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterface"); - var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterfaceTest"); + var source = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterface"); + var target = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfTypeImplementsInterfaceTest"); // Act var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); @@ -220,8 +330,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { // Arrange var compilation = await GetCompilation(); - var source = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface"); - var target = compilation.GetTypeByMetadataName($"{GetType().Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceTest"); + var source = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface"); + var target = compilation.GetTypeByMetadataName($"{Namespace}.IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterfaceTest"); // Act var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs index 1d81f7e41d..6cb3a518a7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs @@ -28,7 +28,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [Fact] public Task IsController_ReturnsFalseForValueType() => IsControllerReturnsFalse(typeof(ValueTypeController)); - [Fact] public Task IsController_ReturnsFalseForGenericType() => IsControllerReturnsFalse(typeof(OpenGenericController<>)); diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs new file mode 100644 index 0000000000..3f7aad5564 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -0,0 +1,311 @@ +// 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.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class SymbolApiResponseMetadataProviderTest + { + private static readonly string Namespace = typeof(SymbolApiResponseMetadataProviderTest).Namespace; + + [Fact] + public async Task GetResponseMetadata_ReturnsEmptySequence_IfNoAttributesArePresent_ForGetAction() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.GetPerson)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsEmptySequence_IfNoAttributesArePresent_ForPostAction() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.PostPerson)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetResponseMetadata_IgnoresProducesAttribute() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesAttribute)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Empty(result); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeIsSpecifiedInConstructor() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeInConstructor)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(201, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeIsSpecifiedInConstructorWithResponseType() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructor)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(202, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeIsSpecifiedInConstructorAndProperty() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeInConstructorAndProperty)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(203, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsValueFromProducesResponseType_WhenStatusCodeAndTypeIsSpecifiedInConstructorAndProperty() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructorAndProperty)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(201, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public async Task GetResponseMetadata_ReturnsValueFromCustomProducesResponseType() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithArguments)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(201, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public async Task GetResponseMetadata_IgnoresCustomResponseTypeMetadataProvider() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomApiResponseMetadataProvider)).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Empty(result); + } + + [Fact] + public Task GetResponseMetadata_IgnoresAttributesWithIncorrectStatusCodeType() + { + return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues( + nameof(GetResponseMetadata_ControllerActionWithAttributes), + nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseTypeWithIncorrectStatusCodeType)); + } + + [Fact] + public Task GetResponseMetadata_IgnoresDerivedAttributesWithoutPropertyOnConstructorArguments() + { + return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues( + nameof(GetResponseMetadata_ControllerActionWithAttributes), + nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithoutArguments)); + } + + private async Task GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues(string typeName, string methodName) + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{typeName}"); + var method = (IMethodSymbol)controller.GetMembers(methodName).First(); + var typeCache = new ApiControllerTypeCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(200, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + Assert.Null(metadata.Convention); + }); + } + + [Fact] + public Task GetStatusCode_ReturnsValueFromConstructor() + { + // Arrange + var actionName = nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeInConstructor); + var expected = 201; + + // Act & Assert + return GetStatusCodeTest(actionName, expected); + } + + [Fact] + public Task GetStatusCode_ReturnsValueFromProperty() + { + // Arrange + var actionName = nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructorAndProperty); + var expected = 201; + + // Act & Assert + return GetStatusCodeTest(actionName, expected); + } + + [Fact] + public Task GetStatusCode_ReturnsValueFromConstructor_WhenTypeIsSpecified() + { + // Arrange + var actionName = nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructor); + var expected = 202; + + // Act & Assert + return GetStatusCodeTest(actionName, expected); + } + + [Fact] + public Task GetStatusCode_Returns200_IfTypeIsNotInteger() + { + // Arrange + var actionName = nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseTypeWithIncorrectStatusCodeType); + var expected = 200; + + // Act & Assert + return GetStatusCodeTest(actionName, expected); + } + + [Fact] + public Task GetStatusCode_ReturnsValueFromDerivedAttributes() + { + // Arrange + var actionName = nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithArguments); + var expected = 201; + + // Act & Assert + return GetStatusCodeTest(actionName, expected); + } + + private async Task GetStatusCodeTest(string actionName, int expected) + { + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(actionName).First(); + var attribute = method.GetAttributes().First(); + + var statusCode = SymbolApiResponseMetadataProvider.GetStatusCode(attribute); + + Assert.Equal(expected, statusCode); + } + + private Task GetResponseMetadataCompilation() => GetCompilation("GetResponseMetadataTests"); + + private Task GetCompilation(string test) + { + var testSource = MvcTestSource.Read(GetType().Name, test); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs new file mode 100644 index 0000000000..9b28469109 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetAttributes_OnMethodWithoutAttributesClass + { + public void Method() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs new file mode 100644 index 0000000000..43b2710d58 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs @@ -0,0 +1,15 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionBase + { + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public virtual void Method() { } + } + + public class GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass : GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionBase + { + [ProducesResponseType(400)] + public override void Method() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs new file mode 100644 index 0000000000..3455cc21dd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs @@ -0,0 +1,22 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetAttributes_WithNewMethodBase + { + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public virtual void VirtualMethod() { } + + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public virtual void NotVirtualMethod() { } + } + + public class GetAttributes_WithNewMethodDerived : GetAttributes_WithNewMethodBase + { + [ProducesResponseType(400)] + public new void VirtualMethod() { } + + [ProducesResponseType(401)] + public new void NotVirtualMethod() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs new file mode 100644 index 0000000000..653abeb19b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs @@ -0,0 +1,8 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetAttributes_WithoutMethodOverridding + { + [ProducesResponseType(201)] + public void Method() { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs new file mode 100644 index 0000000000..34e3b87237 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetResponseMetadata_ControllerWithoutConvention : ControllerBase + { + public ActionResult GetPerson(int id) => null; + + public ActionResult PostPerson(Person person) => null; + } + + public class GetResponseMetadata_ControllerActionWithAttributes : ControllerBase + { + [Produces(typeof(Person))] + public IActionResult ActionWithProducesAttribute(int id) => null; + + [ProducesResponseType(201)] + public IActionResult ActionWithProducesResponseType_StatusCodeInConstructor() => null; + + [ProducesResponseType(typeof(Person), 202)] + public IActionResult ActionWithProducesResponseType_StatusCodeAndTypeInConstructor() => null; + + [ProducesResponseType(200, StatusCode = 203)] + public IActionResult ActionWithProducesResponseType_StatusCodeInConstructorAndProperty() => null; + + [ProducesResponseType(typeof(object), 200, Type = typeof(Person), StatusCode = 201)] + public IActionResult ActionWithProducesResponseType_StatusCodeAndTypeInConstructorAndProperty() => null; + + [CustomResponseType(Type = typeof(Person), StatusCode = 204)] + public IActionResult ActionWithCustomApiResponseMetadataProvider() => null; + + [Produces201ResponseType] + public IActionResult ActionWithCustomProducesResponseTypeAttributeWithoutArguments() => null; + + [Produces201ResponseType(201)] + public IActionResult ActionWithCustomProducesResponseTypeAttributeWithArguments() => null; + + [CustomInvalidProducesResponseType(Type = typeof(Person), StatusCode = "204")] + public IActionResult ActionWithProducesResponseTypeWithIncorrectStatusCodeType() => null; + } + + public class Person { } + + public class CustomResponseTypeAttribute : Attribute, IApiResponseMetadataProvider + { + public Type Type { get; set; } + + public int StatusCode { get; set; } + + public void SetContentTypes(MediaTypeCollection contentTypes) + { + } + } + + public class Produces201ResponseTypeAttribute : ProducesResponseTypeAttribute + { + public Produces201ResponseTypeAttribute() : base(201) { } + + public Produces201ResponseTypeAttribute(int statusCode) : base(statusCode) { } + } + + public class CustomInvalidProducesResponseTypeAttribute : ProducesResponseTypeAttribute + { + private string _statusCode; + + public CustomInvalidProducesResponseTypeAttribute() + : base(0) + { + } + + public new string StatusCode + { + get => _statusCode; + set + { + _statusCode = value; + base.StatusCode = int.Parse(value); + } + } + } +} From 8a77ed23d344c7e438d2772016be3c95f5291ae5 Mon Sep 17 00:00:00 2001 From: jmhmaine Date: Tue, 5 Jun 2018 09:34:15 -0400 Subject: [PATCH 067/316] Added support for ignoring integrity attribute on fallback for the Link TagHelper - Do not include integrity attribute when asp-fallback-intergrity-check is false - Added Unit Tests, manual tests Addresses #7845 --- .../LinkTagHelper.cs | 16 +++++++ .../ScriptTagHelper.cs | 13 ++++++ ...te.HtmlGeneration_Home.Script.Encoded.html | 26 ++++++++--- ...ionWebSite.HtmlGeneration_Home.Script.html | 26 ++++++++--- .../RazorPagesWebSite.SimpleForms.html | 3 +- .../LinkTagHelperTest.cs | 44 ++++++++++++++++++- .../ScriptTagHelperTest.cs | 30 +++++++++++++ .../Views/HtmlGeneration_Home/Link.cshtml | 29 ++++++++++++ .../Views/HtmlGeneration_Home/Script.cshtml | 29 +++++++++++- .../wwwroot/styles/siteIntegrity.css | 6 +++ .../wwwroot/styles/siteIntegrity.js | 6 +++ .../wwwroot/styles/sub/siteIntegrity2.js | 6 +++ .../wwwroot/styles/sub/siteIntegrity3.js | 6 +++ 13 files changed, 226 insertions(+), 14 deletions(-) create mode 100644 test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.css create mode 100644 test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.js create mode 100644 test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity2.js create mode 100644 test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity3.js diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs index 6c1f59f515..f54b613e9c 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("link", Attributes = HrefIncludeAttributeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("link", Attributes = HrefExcludeAttributeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("link", Attributes = FallbackHrefAttributeName, TagStructure = TagStructure.WithoutEndTag)] + [HtmlTargetElement("link", Attributes = FallbackHrefIntegrityCheckAttributeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("link", Attributes = FallbackHrefIncludeAttributeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("link", Attributes = FallbackHrefExcludeAttributeName, TagStructure = TagStructure.WithoutEndTag)] [HtmlTargetElement("link", Attributes = FallbackTestClassAttributeName, TagStructure = TagStructure.WithoutEndTag)] @@ -40,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string HrefIncludeAttributeName = "asp-href-include"; private const string HrefExcludeAttributeName = "asp-href-exclude"; private const string FallbackHrefAttributeName = "asp-fallback-href"; + private const string FallbackHrefIntegrityCheckAttributeName = "asp-fallback-href-integrity-check"; private const string FallbackHrefIncludeAttributeName = "asp-fallback-href-include"; private const string FallbackHrefExcludeAttributeName = "asp-fallback-href-exclude"; private const string FallbackTestClassAttributeName = "asp-fallback-test-class"; @@ -147,6 +149,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(FallbackHrefAttributeName)] public string FallbackHref { get; set; } + /// + /// Boolean value that determines if Integrity Hash will be compared with value. + /// Value defaults to true if not provided. + /// Must be used in conjunction with . + /// + [HtmlAttributeName(FallbackHrefIntegrityCheckAttributeName)] + public bool? FallbackHrefIntegrityCheck { get; set; } + /// /// Value indicating if file version should be appended to the href urls. /// @@ -367,6 +377,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers continue; } + // do not write integrity attribute when FallbackHrefIntegrityCheck is false + if (attribute.Name.Equals("integrity", StringComparison.OrdinalIgnoreCase) && FallbackHrefIntegrityCheck == false) + { + continue; + } + attribute.WriteTo(StringWriter, HtmlEncoder); StringWriter.Write(' '); } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs index 271aa289d8..b0e8289426 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("script", Attributes = FallbackSrcIncludeAttributeName)] [HtmlTargetElement("script", Attributes = FallbackSrcExcludeAttributeName)] [HtmlTargetElement("script", Attributes = FallbackTestExpressionAttributeName)] + [HtmlTargetElement("script", Attributes = FallbackIntegrityCheckAttributeName)] [HtmlTargetElement("script", Attributes = AppendVersionAttributeName)] public class ScriptTagHelper : UrlResolutionTagHelper { @@ -34,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string SrcExcludeAttributeName = "asp-src-exclude"; private const string FallbackSrcAttributeName = "asp-fallback-src"; private const string FallbackSrcIncludeAttributeName = "asp-fallback-src-include"; + private const string FallbackIntegrityCheckAttributeName = "asp-fallback-integrity-check"; private const string FallbackSrcExcludeAttributeName = "asp-fallback-src-exclude"; private const string FallbackTestExpressionAttributeName = "asp-fallback-test"; private const string SrcAttributeName = "src"; @@ -129,6 +131,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(FallbackSrcAttributeName)] public string FallbackSrc { get; set; } + /// + /// Boolean value that determines if Integrity Hash will be compared with value. + /// Value defaults to true if not provided. + /// Must be used in conjunction with . + /// + [HtmlAttributeName(FallbackIntegrityCheckAttributeName)] + public bool? FallbackIntegrityCheck { get; set; } + /// /// Value indicating if file version should be appended to src urls. /// @@ -312,6 +322,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var attribute = attributes[i]; if (!attribute.Name.Equals(SrcAttributeName, StringComparison.OrdinalIgnoreCase)) { + // do not write integrity attribute when fallbackintegrityCheck is false + if (attribute.Name.Equals("integrity", StringComparison.OrdinalIgnoreCase) && FallbackIntegrityCheck == false) continue; + StringWriter.Write(' '); attribute.WriteTo(StringWriter, HtmlEncoder); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html index 680a5729a0..08d2e6d60a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html @@ -19,11 +19,27 @@ // Fallback to globbed src ]]")); + + +]]")); + + +]]")); + + +]]")); -]]")); +]]")); - + - + @@ -110,9 +126,9 @@ - + - + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html index 12b71d6555..ea0f2da3ac 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html @@ -19,11 +19,27 @@ // Fallback to globbed src + + + + + + + + + - + - + - + @@ -110,9 +126,9 @@ - + - + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html index ef06135e61..27ca37082a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html @@ -3,5 +3,4 @@
-
-
\ No newline at end of file +
\ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index ed843fe9e6..4fa536e096 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -263,7 +263,49 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.FallbackTestValue = "hidden"; tagHelper.AppendVersion = true; } - } + }, + // asp-fallback-href-integrity-check Attribute true + { + new TagHelperAttributeList + { + new TagHelperAttribute("asp-fallback-href", "test.css"), + new TagHelperAttribute("asp-fallback-test-class", "hidden"), + new TagHelperAttribute("asp-fallback-test-property", "visibility"), + new TagHelperAttribute("asp-fallback-test-value", "hidden"), + new TagHelperAttribute("asp-append-version", "true"), + new TagHelperAttribute("asp-fallback-href-integrity-check", "true") + }, + tagHelper => + { + tagHelper.FallbackHref = "test.css"; + tagHelper.FallbackTestClass = "hidden"; + tagHelper.FallbackTestProperty = "visibility"; + tagHelper.FallbackTestValue = "hidden"; + tagHelper.AppendVersion = true; + tagHelper.FallbackHrefIntegrityCheck = true; + } + }, + // asp-fallback-href-integrity-check Attribute false + { + new TagHelperAttributeList + { + new TagHelperAttribute("asp-fallback-href", "test.css"), + new TagHelperAttribute("asp-fallback-test-class", "hidden"), + new TagHelperAttribute("asp-fallback-test-property", "visibility"), + new TagHelperAttribute("asp-fallback-test-value", "hidden"), + new TagHelperAttribute("asp-append-version", "true"), + new TagHelperAttribute("asp-fallback-href-integrity-check", "false") + }, + tagHelper => + { + tagHelper.FallbackHref = "test.css"; + tagHelper.FallbackTestClass = "hidden"; + tagHelper.FallbackTestProperty = "visibility"; + tagHelper.FallbackTestValue = "hidden"; + tagHelper.AppendVersion = true; + tagHelper.FallbackHrefIntegrityCheck = false; + } + }, }; } } diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index 97a4214606..a30ee40535 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -156,6 +156,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.FallbackTestExpression = "isavailable()"; } }, + { + new TagHelperAttributeList + { + new TagHelperAttribute("asp-fallback-src", "test.js"), + new TagHelperAttribute("asp-fallback-test", "isavailable()"), + new TagHelperAttribute("asp-fallback-integrity-check", "false") + }, + tagHelper => + { + tagHelper.FallbackSrc = "test.js"; + tagHelper.FallbackTestExpression = "isavailable()"; + tagHelper.FallbackIntegrityCheck = false; + } + }, { new TagHelperAttributeList { @@ -182,6 +196,22 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.FallbackTestExpression = "isavailable()"; } }, + { + new TagHelperAttributeList + { + new TagHelperAttribute("asp-fallback-src", "test.js"), + new TagHelperAttribute("asp-fallback-src-include", "*.js"), + new TagHelperAttribute("asp-fallback-test", "isavailable()"), + new TagHelperAttribute("asp-fallback-integrity-check", "false") + }, + tagHelper => + { + tagHelper.FallbackSrc = "test.js"; + tagHelper.FallbackSrcInclude = "*.css"; + tagHelper.FallbackTestExpression = "isavailable()"; + tagHelper.FallbackIntegrityCheck = false; + } + }, { new TagHelperAttributeList { diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml index 09f614154a..26acbf3b51 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml @@ -46,6 +46,35 @@ asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" /> + + + + + + + + + // Fallback to globbed src + + + + + + + + + + + + + + + + + + @@ -81,7 +93,7 @@ - + @@ -101,7 +113,7 @@ - + @@ -129,7 +141,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html index cb7acce37c..ffa6be6fdf 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html @@ -11,10 +11,10 @@ - + - + @@ -41,6 +41,18 @@ + + + + + + + + + + + + @@ -82,7 +94,7 @@ - + @@ -102,7 +114,7 @@ - + @@ -130,7 +142,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html index 27ca37082a..ef06135e61 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html @@ -3,4 +3,5 @@
-
\ No newline at end of file +
+
\ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index 4fa536e096..833da15fc4 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -264,7 +264,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.AppendVersion = true; } }, - // asp-fallback-href-integrity-check Attribute true + // asp-suppress-fallback-integrity Attribute true { new TagHelperAttributeList { @@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-fallback-test-property", "visibility"), new TagHelperAttribute("asp-fallback-test-value", "hidden"), new TagHelperAttribute("asp-append-version", "true"), - new TagHelperAttribute("asp-fallback-href-integrity-check", "true") + new TagHelperAttribute("asp-suppress-fallback-integrity", "true") }, tagHelper => { @@ -282,10 +282,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.FallbackTestProperty = "visibility"; tagHelper.FallbackTestValue = "hidden"; tagHelper.AppendVersion = true; - tagHelper.FallbackHrefIntegrityCheck = true; + tagHelper.SuppressFallbackIntegrity = true; } }, - // asp-fallback-href-integrity-check Attribute false + // asp-suppress-fallback-integrity Attribute false { new TagHelperAttributeList { @@ -294,7 +294,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-fallback-test-property", "visibility"), new TagHelperAttribute("asp-fallback-test-value", "hidden"), new TagHelperAttribute("asp-append-version", "true"), - new TagHelperAttribute("asp-fallback-href-integrity-check", "false") + new TagHelperAttribute("asp-suppress-fallback-integrity", "false") }, tagHelper => { @@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers tagHelper.FallbackTestProperty = "visibility"; tagHelper.FallbackTestValue = "hidden"; tagHelper.AppendVersion = true; - tagHelper.FallbackHrefIntegrityCheck = false; + tagHelper.SuppressFallbackIntegrity = false; } }, }; diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index a30ee40535..923a76b889 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -161,13 +161,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { new TagHelperAttribute("asp-fallback-src", "test.js"), new TagHelperAttribute("asp-fallback-test", "isavailable()"), - new TagHelperAttribute("asp-fallback-integrity-check", "false") + new TagHelperAttribute("asp-suppress-fallback-integrity", "false") }, tagHelper => { tagHelper.FallbackSrc = "test.js"; tagHelper.FallbackTestExpression = "isavailable()"; - tagHelper.FallbackIntegrityCheck = false; + tagHelper.SuppressFallbackIntegrity = false; } }, { @@ -202,14 +202,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-fallback-src", "test.js"), new TagHelperAttribute("asp-fallback-src-include", "*.js"), new TagHelperAttribute("asp-fallback-test", "isavailable()"), - new TagHelperAttribute("asp-fallback-integrity-check", "false") + new TagHelperAttribute("asp-suppress-fallback-integrity", "false") }, tagHelper => { tagHelper.FallbackSrc = "test.js"; tagHelper.FallbackSrcInclude = "*.css"; tagHelper.FallbackTestExpression = "isavailable()"; - tagHelper.FallbackIntegrityCheck = false; + tagHelper.SuppressFallbackIntegrity = false; } }, { diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml index 26acbf3b51..ffa657373f 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml @@ -46,7 +46,7 @@ asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" /> - + - + + asp-suppress-fallback-integrity="false" /> - + + asp-suppress-fallback-integrity /> @@ -44,7 +44,7 @@ + + + + + + @RenderSection("scripts", required: false) + + diff --git a/benchmarkapps/BasicViews/Views/_ViewImports.cshtml b/benchmarkapps/BasicViews/Views/_ViewImports.cshtml new file mode 100644 index 0000000000..9018c7897f --- /dev/null +++ b/benchmarkapps/BasicViews/Views/_ViewImports.cshtml @@ -0,0 +1 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/benchmarkapps/BasicViews/Views/_ViewStart.cshtml b/benchmarkapps/BasicViews/Views/_ViewStart.cshtml new file mode 100644 index 0000000000..a5f10045db --- /dev/null +++ b/benchmarkapps/BasicViews/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/benchmarkapps/BasicViews/benchmarks.json b/benchmarkapps/BasicViews/benchmarks.json new file mode 100644 index 0000000000..82e0812a21 --- /dev/null +++ b/benchmarkapps/BasicViews/benchmarks.json @@ -0,0 +1,39 @@ +{ + "Default": { + "Client": "Wrk", + "Headers": { + "Cache-Control": "no-cache" + }, + "PresetHeaders": "Html", + "ReadyStateText": "Application started.", + "Source": { + "BranchOrCommit": "dev", + "Project": "benchmarkapps/BasicViews/BasicViews.csproj", + "Repository": "https://github.com/aspnet/mvc.git" + } + }, + "BasicViews.GetHtmlHelpers": { + "Path": "/Home/HtmlHelpers" + }, + "BasicViews.GetTagHelpers": { + "Path": "/Home/Index" + }, + "BasicViews.Post": { + "ClientProperties": { + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua" + }, + "Path": "/Home/Index" + }, + "BasicViews.PostIgnoringToken": { + "ClientProperties": { + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua" + }, + "Path": "/Home/IndexWithoutToken" + }, + "BasicViews.PostWithoutToken": { + "ClientProperties": { + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/post.lua" + }, + "Path": "/Home/IndexWithoutToken" + } +} diff --git a/benchmarkapps/BasicViews/post.lua b/benchmarkapps/BasicViews/post.lua new file mode 100644 index 0000000000..9a7640ee6d --- /dev/null +++ b/benchmarkapps/BasicViews/post.lua @@ -0,0 +1,7 @@ +-- script that POSTs body for requests + +function init(args) + wrk.body = "Age=12&BirthDate=2006-03-01T09%3A51%3A43.041-07%3A00&Name=George" + wrk.headers["Content-Type"] = "application/x-www-form-urlencoded" + wrk.method = "POST" +end diff --git a/benchmarkapps/BasicViews/postWithToken.lua b/benchmarkapps/BasicViews/postWithToken.lua new file mode 100644 index 0000000000..a67309c671 --- /dev/null +++ b/benchmarkapps/BasicViews/postWithToken.lua @@ -0,0 +1,55 @@ +-- script that retrieves an antiforgery token to send in all future requests and adds a body for those requests + +-- do not use wrk's default request +local req = nil + +-- use token for at most maxRequests, default throughout test +local counter = 0 +local maxRequests = -1 + +-- marker that we have completed the first request +local token = nil + +function init(args) + -- initialize first (empty) request + req = wrk.format("GET") +end + +function request() + return req +end + +function response(status, headers, body) + if not token and status == 200 then + local cookie = string.gsub(headers["Set-Cookie"], "^([^;]*)(;.*)?$", "%1") + if not cookie or cookie == "" then + print("Unable to find antiforgery cookie in initial response!") + wrk.thread:stop() + end + + token = string.gsub(body, '^.* name="__RequestVerificationToken".* value="([^"]*)"[ >].*$', "%1") + if not token or token == "" then + print("Unable to find antiforgery token in initial response!") + wrk.thread:stop() + end + + wrk.body = "Age=12&BirthDate=2006-03-01T09%3A51%3A43.041-07%3A00&Name=George&__RequestVerificationToken=" .. token + wrk.headers["Content-Type"] = "application/x-www-form-urlencoded" + wrk.headers["Cookie"] = cookie + wrk.method = "POST" + + req = wrk.format() + return + end + + if not token then + print("Failed initial request! status: " .. status) + wrk.thread:stop() + end + + if counter == maxRequests then + wrk.thread:stop() + end + + counter = counter + 1 +end diff --git a/benchmarkapps/BasicViews/runtimeconfig.template.json b/benchmarkapps/BasicViews/runtimeconfig.template.json new file mode 100644 index 0000000000..0976c49b2c --- /dev/null +++ b/benchmarkapps/BasicViews/runtimeconfig.template.json @@ -0,0 +1,5 @@ +{ + "configProperties": { + "System.GC.Server": true + } +} diff --git a/benchmarkapps/BasicViews/web.config b/benchmarkapps/BasicViews/web.config new file mode 100644 index 0000000000..be088b9ef4 --- /dev/null +++ b/benchmarkapps/BasicViews/web.config @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/benchmarkapps/BasicViews/wwwroot/css/site.css b/benchmarkapps/BasicViews/wwwroot/css/site.css new file mode 100644 index 0000000000..7621c24947 --- /dev/null +++ b/benchmarkapps/BasicViews/wwwroot/css/site.css @@ -0,0 +1,6 @@ +label { + font-size: 1.2em; +} +.test-it { + float: right; +} \ No newline at end of file diff --git a/benchmarkapps/BasicViews/wwwroot/css/site.min.css b/benchmarkapps/BasicViews/wwwroot/css/site.min.css new file mode 100644 index 0000000000..fda38a3c0e --- /dev/null +++ b/benchmarkapps/BasicViews/wwwroot/css/site.min.css @@ -0,0 +1,6 @@ +label { + font-size: 1.3em; +} +.test-it { + float: right; +} \ No newline at end of file diff --git a/benchmarkapps/BasicViews/wwwroot/js/site.js b/benchmarkapps/BasicViews/wwwroot/js/site.js new file mode 100644 index 0000000000..894706a762 --- /dev/null +++ b/benchmarkapps/BasicViews/wwwroot/js/site.js @@ -0,0 +1,3 @@ +console.log("Hello World"); +function test() { +} \ No newline at end of file diff --git a/benchmarkapps/BasicViews/wwwroot/js/site.min.js b/benchmarkapps/BasicViews/wwwroot/js/site.min.js new file mode 100644 index 0000000000..121d23bfa6 --- /dev/null +++ b/benchmarkapps/BasicViews/wwwroot/js/site.min.js @@ -0,0 +1,3 @@ +console.log("Hello Minified World"); +function test() { +} \ No newline at end of file diff --git a/benchmarkapps/README.md b/benchmarkapps/README.md new file mode 100644 index 0000000000..bf86159bcc --- /dev/null +++ b/benchmarkapps/README.md @@ -0,0 +1,15 @@ +## Purpose + +These projects assist in Benchmarking MVC. +They makes it easier to test local changes than having the App in the Benchmarks repo by letting us make changes in MVC branches and use the example commandline below to run the benchmarks against our branches. + +## Usage + +1. Push changes you would like to test to a branch on GitHub +2. Clone aspnet/benchmarks repo to your machine or install the global BenchmarksDriver tool https://www.nuget.org/packages/BenchmarksDriver/ +3. If cloned go to the BenchmarksDriver project +4. Use the following command as a guideline for running a test using your changes + +`benchmarks --server --client -j https://raw.githubusercontent.com/aspnet/MVC/dev/benchmarkaps/BasicApi/BasicApi.json` + +5. For more info/commands see https://github.com/aspnet/benchmarks/blob/dev/src/BenchmarksDriver/README.md diff --git a/build/dependencies.props b/build/dependencies.props index 22e161b819..573fdcc668 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -5,12 +5,27 @@ 0.9.9 0.10.13 + + + 2.1.0 + 2.1.0 + 2.1.0 + 0.42.1 + 2.1.0 + 2.1.0-rc1-final + 2.2.0-preview1-34492 2.2.0-preview1-17090 + 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 + 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 @@ -51,6 +66,7 @@ 1.7.0 2.2.0-preview1-34492 2.2.0-preview1-34492 + 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 2.2.0-preview1-34492 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs new file mode 100644 index 0000000000..42d2a5a5ce --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs @@ -0,0 +1,223 @@ +// 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.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.Net.Http.Headers; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class BasicApiTest : IClassFixture + { + private static readonly byte[] PetBytes = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false) + .GetBytes(@"{ + ""category"" : { + ""name"" : ""Cats"" + }, + ""images"": [ + { + ""url"": ""http://example.com/images/fluffy1.png"" + }, + { + ""url"": ""http://example.com/images/fluffy2.png"" + }, + ], + ""tags"": [ + { + ""name"": ""orange"" + }, + { + ""name"": ""kitty"" + } + ], + ""age"": 2, + ""hasVaccinations"": ""true"", + ""name"" : ""fluffy"", + ""status"" : ""available"" +}"); + + public BasicApiTest(BasicApiFixture fixture) + { + Client = fixture.CreateClient(); + } + + public HttpClient Client { get; } + + [Fact] + public async Task Token_WithUnknownUser_ReturnsForbidden() + { + // Arrange & Act + var response = await Client.GetAsync("/token?username=fallguy@example.com"); + + // Assert + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task Token_WithKnownUser_ReturnsOkAndToken() + { + // Arrange & Act + var response = await Client.GetAsync("/token?username=reader@example.com"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/plain", response.Content.Headers.ContentType.MediaType); + + var token = await response.Content.ReadAsStringAsync(); + Assert.NotNull(token); + Assert.NotEmpty(token); + } + + [Fact] + public async Task FindByStatus_WithNoToken_ReturnsUnauthorized() + { + // Arrange & Act + var response = await Client.GetAsync("/pet/findByStatus?status=available"); + + // Assert + Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); + } + + [Theory] + [InlineData("reader@example.com")] + [InlineData("writer@example.com")] + public async Task FindByStatus_WithToken_ReturnsOkAndPet(string username) + { + // Arrange & Act 1 + var token = await Client.GetStringAsync($"/token?username={username}"); + + // Assert 1 (guard) + Assert.NotEmpty(token); + + // Arrange 2 + var request = new HttpRequestMessage(HttpMethod.Get, "/pet/findByStatus?status=available"); + request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}"); + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); + + var json = await response.Content.ReadAsStringAsync(); + Assert.NotNull(json); + Assert.NotEmpty(json); + } + + [Fact] + public async Task FindById_WithInvalidPetId_ReturnsNotFound() + { + // Arrange & Act 1 + var token = await Client.GetStringAsync("/token?username=reader@example.com"); + + // Assert 1 (guard) + Assert.NotEmpty(token); + + // Arrange 2 + var request = new HttpRequestMessage(HttpMethod.Get, "/pet/100"); + request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}"); + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task FindById_WithValidPetId_ReturnsOkAndPet() + { + // Arrange & Act 1 + var token = await Client.GetStringAsync("/token?username=reader@example.com"); + + // Assert 1 (guard) + Assert.NotEmpty(token); + + // Arrange 2 + var request = new HttpRequestMessage(HttpMethod.Get, "/pet/-1"); + request.Headers.Add(HeaderNames.Authorization, $"Bearer {token}"); + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); + + var json = await response.Content.ReadAsStringAsync(); + Assert.NotNull(json); + Assert.NotEmpty(json); + } + + [Fact] + public async Task AddPet_WithInsufficientClaims_ReturnsForbidden() + { + // Arrange & Act 1 + var token = await Client.GetStringAsync("/token?username=reader@example.com"); + + // Assert 1 (guard) + Assert.NotEmpty(token); + + // Arrange 2 + var request = new HttpRequestMessage(HttpMethod.Post, "/pet") + { + Content = new ByteArrayContent(PetBytes) + { + Headers = + { + { "Content-Type", "application/json" }, + }, + }, + Headers = + { + { HeaderNames.Authorization, $"Bearer {token}" }, + }, + }; + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); + } + + [Fact] + public async Task AddPet_WithValidClaims_ReturnsCreated() + { + // Arrange & Act 1 + var token = await Client.GetStringAsync("/token?username=writer@example.com"); + + // Assert 1 (guard) + Assert.NotEmpty(token); + + // Arrange 2 + var request = new HttpRequestMessage(HttpMethod.Post, "/pet") + { + Content = new ByteArrayContent(PetBytes) + { + Headers = + { + { HeaderNames.ContentType, "application/json" }, + }, + }, + Headers = + { + { HeaderNames.Authorization, $"Bearer {token}" }, + }, + }; + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.Created, response.StatusCode); + var location = response.Headers.Location.ToString(); + Assert.NotNull(location); + Assert.EndsWith("/1", location); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs new file mode 100644 index 0000000000..5f9b0bbcd8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs @@ -0,0 +1,83 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class BasicViewsTest : IClassFixture + { + public BasicViewsTest(BasicViewsFixture fixture) + { + Client = fixture.CreateClient(); + } + + public HttpClient Client { get; } + + [Theory] + [InlineData("/")] + [InlineData("/Home/HtmlHelpers")] + public async Task Get_ReturnsOkAndAntiforgeryToken(string path) + { + // Arrange & Act + var response = await Client.GetAsync(path); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("text/html", response.Content.Headers.ContentType.MediaType); + + var html = await response.Content.ReadAsStringAsync(); + Assert.NotNull(html); + Assert.NotEmpty(html); + + var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(html, "/"); + Assert.NotNull(token); + Assert.NotEmpty(token); + } + + [Theory] + [InlineData("/")] + [InlineData("/Home/HtmlHelpers")] + public async Task Post_ReturnsOkAndNewPerson(string path) + { + // Arrange & Act 1 + var html = await Client.GetStringAsync(path); + + // Assert 1 (guard) + Assert.NotEmpty(html); + + // Arrange 2 + var token = AntiforgeryTestHelper.RetrieveAntiforgeryToken(html, "/"); + var name = Guid.NewGuid().ToString(); + name = name.Substring(startIndex: 0, length: name.LastIndexOf('-')); + var form = new Dictionary + { + { "__RequestVerificationToken", token }, + { "Age", "12" }, + { "BirthDate", "2006-03-01T09:51:43.041-07:00" }, + { "Name", name }, + }; + + var content = new FormUrlEncodedContent(form); + var request = new HttpRequestMessage(HttpMethod.Post, path) + { + Content = content, + }; + + // Act 2 + var response = await Client.SendAsync(request); + + // Assert 2 + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + Assert.NotNull(body); + Assert.Contains($@"value=""{name}""", body); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs new file mode 100644 index 0000000000..56294311a7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs @@ -0,0 +1,21 @@ +// 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 BasicApi; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class BasicApiFixture : MvcTestFixture + { + // Do not leave .db file behind. Also, ensure added pet gets expected id (1) in subsequent runs. + protected override void Dispose(bool disposing) + { + if (disposing) + { + Startup.DropDatabase(Server.Host.Services); + } + + base.Dispose(disposing); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs new file mode 100644 index 0000000000..3ea0c60835 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs @@ -0,0 +1,21 @@ +// 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 BasicViews; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class BasicViewsFixture : MvcTestFixture + { + // Do not leave .db file behind. + protected override void Dispose(bool disposing) + { + if (disposing) + { + Startup.DropDatabase(Server.Host.Services); + } + + base.Dispose(disposing); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index b47eec39bf..ac7be75576 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -24,6 +24,8 @@ + + From 94a7c839984b7143ba83ed2173b70ff25c401f0e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 25 Jun 2018 08:24:30 -0700 Subject: [PATCH 072/316] Change DefaultApiConventions (#7939) * Change DefaultApiConventions * Introduce attributes for matching by name and type. * Move discovery of ApiConventionAttribute to ApiBehaviorApplicationModelProvider. This is required for us to detect during startup if the convention is incorrectly authored. --- .../ApiResponseTypeProvider.cs | 143 +--- .../ApiConventionAttribute.cs | 26 - .../ApiConventionTypeAttribute.cs | 89 +++ .../ApiConventionNameMatchAttribute.cs | 34 + .../ApiConventionNameMatchBehavior.cs | 39 + .../ApiExplorer/ApiConventionResult.cs | 229 ++++++ .../ApiConventionTypeMatchAttribute.cs | 27 + .../ApiConventionTypeMatchBehavior.cs | 22 + .../DefaultApiConventions.cs | 32 +- .../ApiBehaviorApplicationModelProvider.cs | 40 +- .../Properties/Resources.Designer.cs | 14 + .../Resources.resx | 3 + .../ApiResponseTypeProviderTest.cs | 446 +---------- .../ApiConventionTypeAttributeTest.cs | 86 ++ .../ApiExplorer/ApiConventionResultTest.cs | 754 ++++++++++++++++++ ...ApiBehaviorApplicationModelProviderTest.cs | 107 ++- .../ApiExplorerTest.cs | 183 +++++ ...ResponseTypeWithApiConventionController.cs | 39 + 18 files changed, 1678 insertions(+), 635 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs create mode 100644 test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index 6414542f39..e0953ca6e2 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Controllers; @@ -39,57 +38,19 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); var responseMetadataAttributes = GetResponseMetadataAttributes(action); - if (responseMetadataAttributes.Length == 0) + if (responseMetadataAttributes.Count == 0 && + action.Properties.TryGetValue(typeof(ApiConventionResult), out var result)) { - // Action does not have any conventions. Look for conventions on the type. - responseMetadataAttributes = GetResponseMetadataAttributesFromConventions(action); + // Action does not have any conventions. Use conventions on it if present. + var apiConventionResult = (ApiConventionResult)result; + responseMetadataAttributes = apiConventionResult.ResponseMetadataProviders; } var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType); return apiResponseTypes; } - private IApiResponseMetadataProvider[] GetResponseMetadataAttributesFromConventions(ControllerActionDescriptor action) - { - if (action.FilterDescriptors == null) - { - return Array.Empty(); - } - - foreach (var filterDescriptor in action.FilterDescriptors) - { - if (!(filterDescriptor.Filter is ApiConventionAttribute apiConventionAttribute)) - { - continue; - } - - var method = GetConventionMethod(action.MethodInfo, apiConventionAttribute.ConventionType); - if (method != null) - { - return method.GetCustomAttributes(inherit: false) - .OfType() - .ToArray(); - } - } - - return Array.Empty(); - } - - private MethodInfo GetConventionMethod(MethodInfo methodInfo, Type conventions) - { - var conventionMethods = conventions.GetMethods(BindingFlags.Public | BindingFlags.Static); - for (var i = 0; i < conventionMethods.Length; i++) - { - if (IsMatch(methodInfo, conventionMethods[i])) - { - return conventionMethods[i]; - } - } - - return null; - } - - private IApiResponseMetadataProvider[] GetResponseMetadataAttributes(ControllerActionDescriptor action) + private IReadOnlyList GetResponseMetadataAttributes(ControllerActionDescriptor action) { if (action.FilterDescriptors == null) { @@ -107,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } private IList GetApiResponseTypes( - IApiResponseMetadataProvider[] responseMetadataAttributes, + IReadOnlyList responseMetadataAttributes, Type type) { var results = new List(); @@ -240,95 +201,5 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return declaredReturnType; } - - internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod) - { - if (!IsMethodNameMatch(methodInfo.Name, conventionMethod.Name)) - { - return false; - } - - var methodParameters = methodInfo.GetParameters(); - var conventionMethodParameters = conventionMethod.GetParameters(); - if (conventionMethodParameters.Length != methodParameters.Length) - { - return false; - } - - for (var i = 0; i < conventionMethodParameters.Length; i++) - { - if (conventionMethodParameters[i].ParameterType.IsGenericParameter) - { - // Use TModel as wildcard - continue; - } - else if (!IsParameterNameMatch(methodParameters[i].Name, conventionMethodParameters[i].Name) || - !IsParameterTypeMatch(methodParameters[i].ParameterType, conventionMethodParameters[i].ParameterType)) - { - return false; - } - } - - return true; - } - - internal static bool IsMethodNameMatch(string name, string conventionName) - { - if (!name.StartsWith(conventionName, StringComparison.Ordinal)) - { - return false; - } - - if (name.Length == conventionName.Length) - { - return true; - } - - return char.IsUpper(name[conventionName.Length]); - } - - internal static bool IsParameterNameMatch(string name, string conventionName) - { - // Leading underscores could be used to allow multiple parameter names with the same suffix e.g. GetPersonAddress(int personId, int addressId) - // A common convention that allows targeting these category of methods would look like Get(int id, int _id) - conventionName = conventionName.Trim('_'); - - // name = id, conventionName = id - if (string.Equals(name, conventionName, StringComparison.Ordinal)) - { - return true; - } - - if (name.Length <= conventionName.Length) - { - return false; - } - - // name = personId, conventionName = id - var index = name.Length - conventionName.Length - 1; - if (!char.IsLower(name[index])) - { - return false; - } - - index++; - if (name[index] != char.ToUpper(conventionName[0])) - { - return false; - } - - index++; - return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; - } - - internal static bool IsParameterTypeMatch(Type parameterType, Type conventionParameterType) - { - if (conventionParameterType == typeof(object)) - { - return true; - } - - return conventionParameterType.IsAssignableFrom(parameterType); - } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs deleted file mode 100644 index 0cd749ca8c..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionAttribute.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.AspNetCore.Mvc.Filters; - -namespace Microsoft.AspNetCore.Mvc -{ - [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] - public sealed class ApiConventionAttribute : Attribute, IFilterMetadata - { - public ApiConventionAttribute(Type conventionType) - { - ConventionType = conventionType ?? throw new ArgumentNullException(nameof(conventionType)); - - if (!ConventionType.IsSealed || !ConventionType.IsAbstract) - { - // Conventions must be static viz abstract + sealed. - throw new ArgumentException(Resources.FormatApiConventionMustBeStatic(conventionType), nameof(conventionType)); - } - } - - public Type ConventionType { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs new file mode 100644 index 0000000000..9c44452961 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs @@ -0,0 +1,89 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.Extensions.Internal; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// API conventions to be applied to an assembly containing MVC controllers or a single controller. + /// + /// API conventions are used to influence the output of ApiExplorer. + /// Conventions must be static types. Methods in a convention are + /// matched to an action method using rules specified by + /// that may be applied to a method name or it's parameters and + /// that are applied to parameters. + /// + /// + /// When no attributes are found specifying the behavior, MVC matches method names and parameter names are matched + /// using and parameter types are matched + /// using . + /// + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = true, Inherited = true)] + public sealed class ApiConventionTypeAttribute : Attribute + { + /// + /// Initializes an instance using . + /// + /// + /// The of the convention. + /// + /// Conventions must be static types. Methods in a convention are + /// matched to an action method using rules specified by + /// that may be applied to a method name or it's parameters and + /// that are applied to parameters. + /// + /// + public ApiConventionTypeAttribute(Type conventionType) + { + ConventionType = conventionType ?? throw new ArgumentNullException(nameof(conventionType)); + EnsureValid(conventionType); + } + + /// + /// Gets the convention type. + /// + public Type ConventionType { get; } + + private static void EnsureValid(Type conventionType) + { + if (!conventionType.IsSealed || !conventionType.IsAbstract) + { + // Conventions must be static viz abstract + sealed. + throw new ArgumentException(Resources.FormatApiConventionMustBeStatic(conventionType), nameof(conventionType)); + } + + foreach (var method in conventionType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + { + var unsupportedAttributes = method.GetCustomAttributes(inherit: true) + .Where(attribute => !IsAllowedAttribute(attribute)) + .ToArray(); + + if (unsupportedAttributes.Length == 0) + { + continue; + } + + var methodDisplayName = TypeNameHelper.GetTypeDisplayName(method.DeclaringType) + "." + method.Name; + var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention( + methodDisplayName, + Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine, + $"{nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"); + + throw new ArgumentException(errorMessage, nameof(conventionType)); + } + } + + private static bool IsAllowedAttribute(object attribute) + { + return attribute is ProducesResponseTypeAttribute || + attribute is ApiConventionNameMatchAttribute; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs new file mode 100644 index 0000000000..2da8950dec --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs @@ -0,0 +1,34 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Determines the matching behavior an API convention method or parameter by name. + /// for supported options. + /// . + /// + /// + /// is used if no value for this + /// attribute is specified on a convention method or parameter. + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Method, AllowMultiple = false, Inherited = false)] + public sealed class ApiConventionNameMatchAttribute : Attribute + { + /// + /// Initializes a new instance of . + /// + /// The . + public ApiConventionNameMatchAttribute(ApiConventionNameMatchBehavior matchBehavior) + { + MatchBehavior = matchBehavior; + } + + /// + /// Gets the . + /// + public ApiConventionNameMatchBehavior MatchBehavior { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs new file mode 100644 index 0000000000..b4775fbf05 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs @@ -0,0 +1,39 @@ +// 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.ApiExplorer +{ + /// + /// The behavior for matching the name of a convention parameter or method. + /// + public enum ApiConventionNameMatchBehavior + { + /// + /// Matches any name. Use this if the parameter or method name does not need to be matched. + /// + Any, + + /// + /// The parameter or method name must exactly match the convention. + /// + Exact, + + /// + /// The parameter or method name in the convention is a proper prefix. + /// + /// Casing is used to delineate words in a given name. For instance, with this behavior + /// the convention name "Get" will match "Get", "GetPerson" or "GetById", but not "getById", "Getaway". + /// + /// + Prefix, + + /// + /// The parameter or method name in the convention is a proper suffix. + /// + /// Casing is used to delineate words in a given name. For instance, with this behavior + /// the convention name "id" will match "id", or "personId" but not "grid" or "personid". + /// + /// + Suffix, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs new file mode 100644 index 0000000000..ca4d85d177 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs @@ -0,0 +1,229 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Metadata associated with an action method via API convention. + /// + public sealed class ApiConventionResult + { + public ApiConventionResult(IReadOnlyList responseMetadataProviders) + { + ResponseMetadataProviders = responseMetadataProviders ?? + throw new ArgumentNullException(nameof(responseMetadataProviders)); + } + + public IReadOnlyList ResponseMetadataProviders { get; } + + internal static bool TryGetApiConvention( + MethodInfo method, + ApiConventionTypeAttribute[] apiConventionAttributes, + out ApiConventionResult result) + { + foreach (var attribute in apiConventionAttributes) + { + var conventionMethod = GetConventionMethod(method, attribute.ConventionType); + if (conventionMethod != null) + { + var metadataProviders = conventionMethod.GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); + + result = new ApiConventionResult(metadataProviders); + return true; + } + } + + result = null; + return false; + } + + private static MethodInfo GetConventionMethod(MethodInfo method, Type conventionType) + { + foreach (var conventionMethod in conventionType.GetMethods()) + { + if (IsMatch(method, conventionMethod)) + { + return conventionMethod; + } + } + + return null; + } + + internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod) + { + return MethodMatches() && ParametersMatch(); + + bool MethodMatches() + { + var methodNameMatchBehavior = GetNameMatchBehavior(conventionMethod); + if (!IsNameMatch(methodInfo.Name, conventionMethod.Name, methodNameMatchBehavior)) + { + return false; + } + + return true; + } + + bool ParametersMatch() + { + var methodParameters = methodInfo.GetParameters(); + var conventionMethodParameters = conventionMethod.GetParameters(); + + for (var i = 0; i < conventionMethodParameters.Length; i++) + { + var conventionParameter = conventionMethodParameters[i]; + if (conventionParameter.IsDefined(typeof(ParamArrayAttribute))) + { + return true; + } + + if (methodParameters.Length <= i) + { + return false; + } + + var nameMatchBehavior = GetNameMatchBehavior(conventionParameter); + var typeMatchBehavior = GetTypeMatchBehavior(conventionParameter); + + if (!IsTypeMatch(methodParameters[i].ParameterType, conventionParameter.ParameterType, typeMatchBehavior) || + !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior)) + { + return false; + } + } + + // Ensure convention has at least as many parameters as the method. params convention argument are handled + // inside the for loop. + return methodParameters.Length == conventionMethodParameters.Length; + } + } + + internal static ApiConventionNameMatchBehavior GetNameMatchBehavior(ICustomAttributeProvider attributeProvider) + { + var attribute = GetCustomAttribute(attributeProvider); + return attribute?.MatchBehavior ?? ApiConventionNameMatchBehavior.Exact; + } + + internal static ApiConventionTypeMatchBehavior GetTypeMatchBehavior(ICustomAttributeProvider attributeProvider) + { + var attribute = GetCustomAttribute(attributeProvider); + return attribute?.MatchBehavior ?? ApiConventionTypeMatchBehavior.AssignableFrom; + } + + private static TAttribute GetCustomAttribute(ICustomAttributeProvider attributeProvider) + { + var attributes = attributeProvider.GetCustomAttributes(inherit: false); + for (var i = 0; i < attributes.Length; i++) + { + if (attributes[i] is TAttribute attribute) + { + return attribute; + } + } + + return default; + } + + internal static bool IsNameMatch(string name, string conventionName, ApiConventionNameMatchBehavior nameMatchBehavior) + { + switch (nameMatchBehavior) + { + case ApiConventionNameMatchBehavior.Any: + return true; + + case ApiConventionNameMatchBehavior.Exact: + return string.Equals(name, conventionName, StringComparison.Ordinal); + + case ApiConventionNameMatchBehavior.Prefix: + return IsNameMatchPrefix(); + + case ApiConventionNameMatchBehavior.Suffix: + return IsNameMatchSuffix(); + + default: + return false; + } + + bool IsNameMatchPrefix() + { + if (name.Length < conventionName.Length) + { + return false; + } + + if (name.Length == conventionName.Length) + { + // name = "Post", conventionName = "Post" + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + if (!name.StartsWith(conventionName, StringComparison.Ordinal)) + { + // name = "GetPerson", conventionName = "Post" + return false; + } + + // Check for name = "PostPerson", conventionName = "Post" + // Verify the first letter after the convention name is upper case. In this case 'P' from "Person" + return char.IsUpper(name[conventionName.Length]); + } + + bool IsNameMatchSuffix() + { + if (name.Length < conventionName.Length) + { + // name = "person", conventionName = "personName" + return false; + } + + if (name.Length == conventionName.Length) + { + // name = id, conventionName = id + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + // Check for name = personName, conventionName = name + var index = name.Length - conventionName.Length - 1; + if (!char.IsLower(name[index])) + { + // Verify letter before "name" is lowercase. In this case the letter 'n' at the end of "person" + return false; + } + + index++; + if (name[index] != char.ToUpper(conventionName[0])) + { + // Verify the first letter from convention is upper case. In this case 'n' from "name" + return false; + } + + // Match the remaining letters with exact case. i.e. match "ame" from "personName", "name" + index++; + return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; + } + } + + internal static bool IsTypeMatch(Type type, Type conventionType, ApiConventionTypeMatchBehavior typeMatchBehavior) + { + switch (typeMatchBehavior) + { + case ApiConventionTypeMatchBehavior.Any: + return true; + + case ApiConventionTypeMatchBehavior.AssignableFrom: + return conventionType.IsAssignableFrom(type); + + default: + return false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs new file mode 100644 index 0000000000..657da6ef3d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs @@ -0,0 +1,27 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + /// + /// Determines the matching behavior an API convention parameter by type. + /// for supported options. + /// . + /// + /// + /// is used if no value for this + /// attribute is specified on a convention parameter. + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + public sealed class ApiConventionTypeMatchAttribute : Attribute + { + public ApiConventionTypeMatchAttribute(ApiConventionTypeMatchBehavior matchBehavior) + { + MatchBehavior = matchBehavior; + } + + public ApiConventionTypeMatchBehavior MatchBehavior { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs new file mode 100644 index 0000000000..300c7255f9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs @@ -0,0 +1,22 @@ +// 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.ApiExplorer +{ + /// + /// The behavior for matching the name of a convention parameter. + /// + public enum ApiConventionTypeMatchBehavior + { + /// + /// Matches any type. Use this if the parameter does not need to be matched. + /// + Any, + + /// + /// The parameter in the convention is the exact type or a subclass of the type + /// specified in the convention. + /// + AssignableFrom, + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs index a48bd43bb7..46b3ec6e80 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ApiExplorer; namespace Microsoft.AspNetCore.Mvc { @@ -9,23 +10,40 @@ namespace Microsoft.AspNetCore.Mvc { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] - public static void Get(object id) { } - - [ProducesResponseType(StatusCodes.Status200OK)] - public static void Get() { } + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Get( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id) { } [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public static void Post(TModel model) { } + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Post( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object model) { } [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public static void Put(object id, TModel model) { } + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Put( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id, + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object model) { } [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] - public static void Delete(object id) { } + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Delete( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id) { } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index b5181b6706..7105b9a4df 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -68,11 +69,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (isApiController) { InferBoundPropertyModelPrefixes(controllerModel); - - AddGloballyConfiguredApiConventions(controllerModel); } var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null); + var conventions = controllerModel.Attributes.OfType().ToArray(); + if (conventions.Length == 0) + { + var controllerAssembly = controllerModel.ControllerType.Assembly; + conventions = controllerAssembly.GetCustomAttributes().ToArray(); + } foreach (var actionModel in controllerModel.Actions) { @@ -90,25 +95,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal InferParameterModelPrefixes(actionModel); AddMultipartFormDataConsumesAttribute(actionModel); + + DiscoverApiConvention(actionModel, conventions); } } } - internal static void AddGloballyConfiguredApiConventions(ControllerModel controllerModel) - { - if (controllerModel.Filters.OfType().Any()) - { - // ApiControllerAttribute is already associated with controller. Do not look for conventions configured at assembly. - return; - } - - var assembly = controllerModel.ControllerType.Assembly; - foreach (var attribute in assembly.GetCustomAttributes()) - { - controllerModel.Filters.Add(attribute); - } - } - // Internal for unit testing internal void AddMultipartFormDataConsumesAttribute(ActionModel actionModel) { @@ -250,6 +242,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal return bindingSource; } + internal static void DiscoverApiConvention(ActionModel actionModel, ApiConventionTypeAttribute[] apiConventionAttributes) + { + if (actionModel.Filters.OfType().Any()) + { + // If an action already has providers, don't discover any from conventions. + return; + } + + if (ApiConventionResult.TryGetApiConvention(actionModel.ActionMethod, apiConventionAttributes, out var result)) + { + actionModel.Properties[typeof(ApiConventionResult)] = result; + } + } + private bool ParameterExistsInAnyRoute(ActionModel actionModel, string parameterName) { foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(actionModel)) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 1221fccdef..7612620733 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1494,6 +1494,20 @@ namespace Microsoft.AspNetCore.Mvc.Core internal static string FormatInvalidTypeTForActionResultOfT(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("InvalidTypeTForActionResultOfT"), p0, p1); + /// + /// Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}. + /// + internal static string ApiConvention_UnsupportedAttributesOnConvention + { + get => GetString("ApiConvention_UnsupportedAttributesOnConvention"); + } + + /// + /// Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}. + /// + internal static string FormatApiConvention_UnsupportedAttributesOnConvention(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiConvention_UnsupportedAttributesOnConvention"), p0, p1, p2); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 98aa14bda6..652f0df157 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -448,4 +448,7 @@ Invalid type parameter '{0}' specified for '{1}'. + + Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index dd72f88b2e..99e862dcf4 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -17,304 +17,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { public class ApiResponseTypeProviderTest { - [Theory] - [InlineData("id", "model")] - [InlineData("id", "person")] - [InlineData("id", "i")] - public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotSuffix(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsParameterNameMatch_ReturnsFalse_IfConventionNameIsNotExactCaseSensitiveMatch() - { - // Arrange - var parameterName = "Id"; - var conventionName = "id"; - - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("rid", "id")] - [InlineData("candid", "id")] - [InlineData("colocation", "location")] - public void IsParamterNameMatch_ReturnsFalse_IfConventionNameIsNotProperSuffix(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("id", "id")] - [InlineData("model", "model")] - public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatch(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("id", "_id")] - [InlineData("model", "_model")] - public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsExactMatchIgnoringLeadingUnderscores(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("personId", "id")] - [InlineData("userModel", "model")] - [InlineData("beaconLocation", "Location")] - public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffix(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("personId", "_id")] - [InlineData("userModel", "_model")] - [InlineData("userModel", "__model")] - public void IsParamterNameMatch_ReturnsTrue_IfConventionNameIsProperSuffixIgnoringLeadingUnderscores(string parameterName, string conventionName) - { - // Act - var result = ApiResponseTypeProvider.IsParameterNameMatch(parameterName, conventionName); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsParameterTypeMatch_ReturnsFalse_ForUnrelatedTypes() - { - // Arrange - var type = typeof(string); - var conventionType = typeof(int); - - // Act - var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsParameterTypeMatch_ReturnsFalse_IfTypeIsBaseClassOfConvention() - { - // Arrange - var type = typeof(BaseModel); - var conventionType = typeof(DerivedModel); - - // Act - var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsExact() - { - // Arrange - var type = typeof(Uri); - var conventionType = typeof(Uri); - - // Act - var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsParameterTypeMatch_ReturnsTrue_IfTypeIsSubtypeOfConvention() - { - // Arrange - var type = typeof(DerivedModel); - var conventionType = typeof(BaseModel); - - // Act - var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData(typeof(int))] - [InlineData(typeof(DerivedModel))] - public void IsParameterTypeMatch_ReturnsTrue_IfConventionTypeIsObject(Type type) - { - // Arrange - var conventionType = typeof(object); - - // Act - var result = ApiResponseTypeProvider.IsParameterTypeMatch(type, conventionType); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("Get", "Post")] - [InlineData("Post", "Get")] - [InlineData("PostPerson", "Put")] - public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotPrefix(string methodName, string conventionMethodName) - { - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("PostalService", "Post")] - [InlineData("Listings", "List")] - [InlineData("Putt", "Put")] - public void IsMethodNameMatch_ReturnsFalse_IfMethodIsNotProperPrefix(string methodName, string conventionMethodName) - { - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsExactMatch() - { - // Arrange - var methodName = "Post"; - var conventionMethodName = "Post"; - - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsExactMatchWithDifferentCasing() - { - // Arrange - var methodName = "post"; - var conventionMethodName = "Post"; - - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData("PostPerson", "Post")] - [InlineData("GetById", "Get")] - [InlineData("SearchList", "Search")] - public void IsMethodNameMatch_ReturnsTrue_IfMethodNameIsProperSuffix(string methodName, string conventionMethodName) - { - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsMethodNameMatch_ReturnsFalse_IfMethodNameIsProperSuffix_WithDifferentCasing() - { - // Arrange - var methodName = "getById"; - var conventionMethodName = "Get"; - - // Act - var result = ApiResponseTypeProvider.IsMethodNameMatch(methodName, conventionMethodName); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IfMethodNamesAreNotMatches() - { - // Arrange - var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Post)); - var method = typeof(TestController).GetMethod(nameof(TestController.GetUser)); - - // Act - var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IfParameterCountsDoNotMatch() - { - // Arrange - var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) }); - var method = typeof(TestController).GetMethod(nameof(TestController.GetUserLocation)); - - // Act - var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsTrue_ForMethodWithObjectParameter() - { - // Arrange - var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Get), new[] { typeof(object) }); - var method = typeof(TestController).GetMethod(nameof(TestController.GetUser)); - - // Act - var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsMatch_ReturnsTrue_ForConventionWithGenericParameter() - { - // Arrange - var conventionMethod = typeof(DefaultApiConventions).GetMethod(nameof(DefaultApiConventions.Put)); - var method = typeof(TestController).GetMethod(nameof(TestController.PutModel)); - - // Act - var result = ApiResponseTypeProvider.IsMatch(method, conventionMethod); - - // Assert - Assert.True(result); - } - [Fact] public void GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresent() { @@ -322,9 +24,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var actionDescriptor = GetControllerActionDescriptor( typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController), nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)); - - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new[] + { + new ProducesResponseTypeAttribute(201), + new ProducesResponseTypeAttribute(404), + }); var provider = GetProvider(); @@ -359,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } - [ApiConvention(typeof(DefaultApiConventions))] + [ApiConventionType(typeof(DefaultApiConventions))] public class GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController : ControllerBase { [Produces(typeof(BaseModel))] @@ -369,14 +73,19 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } [Fact] - public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventions() + public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem() { // Arrange var actionDescriptor = GetControllerActionDescriptor( typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); + + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(400), + new ProducesResponseTypeAttribute(404), + }); var provider = GetProvider(); @@ -409,133 +118,12 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } - [ApiConvention(typeof(DefaultApiConventions))] + [ApiConventionType(typeof(DefaultApiConventions))] public class GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController : ControllerBase { public Task> DeleteBase(int id) => null; } - [Fact] - public void GetApiResponseTypes_ReturnsResponseTypesFromCustomConventions() - { - // Arrange - var actionDescriptor = GetControllerActionDescriptor( - typeof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController), - nameof(GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController.SearchModel)); - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); - - var provider = GetProvider(); - - // Act - var result = provider.GetApiResponseTypes(actionDescriptor); - - // Assert - Assert.Collection( - result.OrderBy(r => r.StatusCode), - responseType => - { - Assert.Equal(206, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }, - responseType => - { - Assert.Equal(406, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }); - } - - [ApiConvention(typeof(SearchApiConventions))] - public class GetApiResponseTypes_ReturnsResponseTypesFromCustomConventionsController : ControllerBase - { - public Task> SearchModel(string searchTerm, int page) => null; - } - - [Fact] - public void GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConvention_WhenMultipleConventionsArePresent() - { - // Arrange - var actionDescriptor = GetControllerActionDescriptor( - typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController), - nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.SearchModel)); - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); - filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); - - var provider = GetProvider(); - - // Act - var result = provider.GetApiResponseTypes(actionDescriptor); - - // Assert - Assert.Collection( - result.OrderBy(r => r.StatusCode), - responseType => - { - Assert.Equal(206, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }, - responseType => - { - Assert.Equal(406, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }); - } - - [ApiConvention(typeof(DefaultApiConventions))] - [ApiConvention(typeof(SearchApiConventions))] - public class GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController : ControllerBase - { - public Task> Get(int id) => null; - - public Task> SearchModel(string searchTerm, int page) => null; - } - - [Fact] - public void GetApiResponseTypes_ReturnsResponseTypesFromDefaultConvention_WhenMultipleConventionsArePresent() - { - // Arrange - var actionDescriptor = GetControllerActionDescriptor( - typeof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController), - nameof(GetApiResponseTypes_ReturnsResponseTypesFromFirstMatchingConventionController.Get)); - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); - filter = new FilterDescriptor(new ApiConventionAttribute(typeof(SearchApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); - - var provider = GetProvider(); - - // Act - var result = provider.GetApiResponseTypes(actionDescriptor); - - // Assert - Assert.Collection( - result.OrderBy(r => r.StatusCode), - responseType => - { - Assert.Equal(200, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }, - responseType => - { - Assert.Equal(404, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); - Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); - }); - } - [Fact] public void GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatch() { @@ -543,8 +131,6 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var actionDescriptor = GetControllerActionDescriptor( typeof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController), nameof(GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController.PostModel)); - var filter = new FilterDescriptor(new ApiConventionAttribute(typeof(DefaultApiConventions)), FilterScope.Controller); - actionDescriptor.FilterDescriptors.Add(filter); var provider = GetProvider(); @@ -565,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } - [ApiConvention(typeof(DefaultApiConventions))] + [ApiConventionType(typeof(DefaultApiConventions))] public class GetApiResponseTypes_ReturnsDefaultResultsIfNoConventionsMatchController : ControllerBase { public Task> PostModel(int id, BaseModel model) => null; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs new file mode 100644 index 0000000000..edc7ce7747 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs @@ -0,0 +1,86 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class ApiConventionTypeAttributeTest + { + [Fact] + public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute() + { + // Arrange + var expected = $"Method {typeof(ConventionWithProducesAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + typeof(ProducesAttribute).FullName + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionTypeAttribute(typeof(ConventionWithProducesAttribute)), + "conventionType", + expected); + } + + public static class ConventionWithProducesAttribute + { + [Produces(typeof(void))] + public static void Get() { } + } + + [Fact] + public void Constructor_ThrowsIfConventionMethodHasRouteAttribute() + { + // Arrange + var expected = $"Method {typeof(ConventionWithRouteAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + typeof(HttpGetAttribute).FullName + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionTypeAttribute(typeof(ConventionWithRouteAttribute)), + "conventionType", + expected); + } + + public static class ConventionWithRouteAttribute + { + [HttpGet("url")] + public static void Get() { } + } + + [Fact] + public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention() + { + // Arrange + var expected = $"Method {typeof(ConventionWitUnsupportedAttributes).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + string.Join(Environment.NewLine, typeof(ProducesAttribute).FullName, typeof(ServiceFilterAttribute).FullName, typeof(AuthorizeAttribute).FullName) + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionTypeAttribute(typeof(ConventionWitUnsupportedAttributes)), + "conventionType", + expected); + } + + public static class ConventionWitUnsupportedAttributes + { + [ProducesResponseType(400)] + [Produces(typeof(void))] + [ServiceFilter(typeof(object))] + [Authorize] + public static void Get() { } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs new file mode 100644 index 0000000000..69079bd8b0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs @@ -0,0 +1,754 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public class ApiConventionResultTest + { + [Fact] + public void GetApiConvention_ReturnsNull_IfNoConventionMatches() + { + // Arrange + var method = typeof(GetApiConvention_ReturnsNull_IfNoConventionMatchesController).GetMethod(nameof(GetApiConvention_ReturnsNull_IfNoConventionMatchesController.NoMatch)); + var attribute = new ApiConventionTypeAttribute(typeof(DefaultApiConventions)); + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, new[] { attribute }, out var conventionResult); + + // Assert + Assert.False(result); + Assert.Null(conventionResult); + } + + public class GetApiConvention_ReturnsNull_IfNoConventionMatchesController + { + public IActionResult NoMatch(int id) => null; + } + + [Fact] + public void GetApiConvention_ReturnsResultFromConvention() + { + // Arrange + var method = typeof(GetApiConvention_ReturnsResultFromConventionController) + .GetMethod(nameof(GetApiConvention_ReturnsResultFromConventionController.Match)); + var attribute = new ApiConventionTypeAttribute(typeof(GetApiConvention_ReturnsResultFromConventionType)); + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, new[] { attribute }, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(201, r.StatusCode), + r => Assert.Equal(403, r.StatusCode)); + } + + public class GetApiConvention_ReturnsResultFromConventionController + { + public IActionResult Match(int id) => null; + } + + public static class GetApiConvention_ReturnsResultFromConventionType + { + [ProducesResponseType(200)] + [ProducesResponseType(202)] + [ProducesResponseType(404)] + public static void Get(int id) { } + + [ProducesResponseType(201)] + [ProducesResponseType(403)] + public static void Match(int id) { } + } + + [Fact] + public void GetApiConvention_ReturnsResultFromFirstMatchingConvention() + { + // Arrange + var method = typeof(GetApiConvention_ReturnsResultFromFirstMatchingConventionController) + .GetMethod(nameof(GetApiConvention_ReturnsResultFromFirstMatchingConventionController.Get)); + var attributes = new[] + { + new ApiConventionTypeAttribute(typeof(GetApiConvention_ReturnsResultFromConventionType)), + new ApiConventionTypeAttribute(typeof(DefaultApiConventions)), + }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, attributes, result: out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(200, r.StatusCode), + r => Assert.Equal(202, r.StatusCode), + r => Assert.Equal(404, r.StatusCode)); + } + + public class GetApiConvention_ReturnsResultFromFirstMatchingConventionController + { + public IActionResult Get(int id) => null; + } + + [Fact] + public void GetApiConvention_GetAction_MatchesDefaultConvention() + { + // Arrange + var method = typeof(DefaultConventionController) + .GetMethod(nameof(DefaultConventionController.GetUser)); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, attributes, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(200, r.StatusCode), + r => Assert.Equal(404, r.StatusCode)); + } + + [Fact] + public void GetApiConvention_PostAction_MatchesDefaultConvention() + { + // Arrange + var method = typeof(DefaultConventionController) + .GetMethod(nameof(DefaultConventionController.PostUser)); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, attributes, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(201, r.StatusCode), + r => Assert.Equal(400, r.StatusCode)); + } + + [Fact] + public void GetApiConvention_PutAction_MatchesDefaultConvention() + { + // Arrange + var method = typeof(DefaultConventionController) + .GetMethod(nameof(DefaultConventionController.PutUser)); + var conventions = new[] + { + new ApiConventionTypeAttribute(typeof(DefaultApiConventions)), + }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, conventions, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(204, r.StatusCode), + r => Assert.Equal(400, r.StatusCode), + r => Assert.Equal(404, r.StatusCode)); + } + + [Fact] + public void GetApiConvention_DeleteAction_MatchesDefaultConvention() + { + // Arrange + var method = typeof(DefaultConventionController) + .GetMethod(nameof(DefaultConventionController.Delete)); + var conventions = new[] + { + new ApiConventionTypeAttribute(typeof(DefaultApiConventions)), + }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, conventions, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.Equal(200, r.StatusCode), + r => Assert.Equal(400, r.StatusCode), + r => Assert.Equal(404, r.StatusCode)); + } + + public class DefaultConventionController + { + public IActionResult GetUser(Guid id) => null; + + public IActionResult PostUser(User user) => null; + + public IActionResult PutUser(Guid userId, User user) => null; + + public IActionResult Delete(Guid userId) => null; + } + + public class User { } + + [Theory] + [InlineData("Method", "method")] + [InlineData("Method", "ConventionMethod")] + [InlineData("p", "model")] + [InlineData("person", "model")] + public void IsNameMatch_WithAny_AlwaysReturnsTrue(string name, string conventionName) + { + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "Name"; + var conventionName = "name"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "Name"; + var conventionName = "Different"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSubString() + { + // Arrange + var name = "RegularName"; + var conventionName = "Regular"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSuperString() + { + // Arrange + var name = "Regular"; + var conventionName = "RegularName"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsTrue_IfExactMatch() + { + // Arrange + var name = "parameterName"; + var conventionName = "parameterName"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNamesAreExact() + { + // Arrange + var name = "PostPerson"; + var conventionName = "PostPerson"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNameIsProperPrefix() + { + // Arrange + var name = "PostPerson"; + var conventionName = "Post"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "GetPerson"; + var conventionName = "Post"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "GetPerson"; + var conventionName = "post"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() + { + // Arrange + var name = "Postman"; + var conventionName = "Post"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsSuffix() + { + // Arrange + var name = "GoPost"; + var conventionName = "Post"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "name"; + var conventionName = "diff"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNameIsNotSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "idx"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsExact() + { + // Arrange + var name = "test"; + var conventionName = "test"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameDiffersInCase() + { + // Arrange + var name = "test"; + var conventionName = "Test"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsProperSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "id"; + + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("candid", "id")] + [InlineData("canDid", "id")] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameIsNotProperSuffix(string name, string conventionName) + { + // Act + var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(typeof(object), typeof(object))] + [InlineData(typeof(int), typeof(void))] + [InlineData(typeof(string), typeof(DateTime))] + public void IsTypeMatch_WithAny_ReturnsTrue(Type type, Type conventionType) + { + // Act + var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssinableFrom_ReturnsTrueForExact() + { + // Arrange + var type = typeof(Base); + var conventionType = typeof(Base); + + // Act + var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssinableFrom_ReturnsTrueForDerived() + { + // Arrange + var type = typeof(Derived); + var conventionType = typeof(Base); + + // Act + var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssinableFrom_ReturnsFalseForBaseTypes() + { + // Arrange + var type = typeof(Base); + var conventionType = typeof(Derived); + + // Act + var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsTypeMatch_WithAssinableFrom_ReturnsFalseForUnrelated() + { + // Arrange + var type = typeof(string); + var conventionType = typeof(Derived); + + // Act + var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfMethodNamesDoNotMatch() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Post)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IMethodHasMoreParametersThanConvention() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetNoArgs)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfMethodHasFewerParametersThanConvention() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetTwoArgs)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfParametersDoNotMatch() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetParameterNotMatching)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Get)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_IfParamsArrayMatchesRemainingArguments() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Search)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Search)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMatch_WithEmpty_MatchesMethodWithNoParameters() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.SearchEmpty)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.SearchWithParams)); + + // Act + var result = ApiConventionResult.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsExact_WhenNoAttributesArePresent() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Exact; + var attributes = new object[0]; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsExact_WhenNoNameMatchBehaviorAttributeIsSpecified() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Exact; + var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Prefix; + var attributes = new object[] + { + new CLSCompliantAttribute(false), + new ApiConventionNameMatchAttribute(expected), + new ProducesResponseTypeAttribute(200) } + ; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoAttributesArePresent() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.AssignableFrom; + var attributes = new object[0]; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoMatchingAttributesArePresent() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.AssignableFrom; + var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.Any; + var attributes = new object[] + { + new CLSCompliantAttribute(false), + new ApiConventionTypeMatchAttribute(expected), + new ProducesResponseTypeAttribute(200) } + ; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionResult.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + public class Base { } + + public class Derived : Base { } + + public class TestController + { + public IActionResult Get(int id) => null; + + public IActionResult Search(string searchTerm, bool sortDescending, int page) => null; + + public IActionResult SearchEmpty() => null; + } + + public static class TestConvention + { + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Get(int id) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetNoArgs() { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetTwoArgs(int id, string name) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Post(Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void GetParameterNotMatching([ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.AssignableFrom)] Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void Search( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Exact)] + string searchTerm, + params object[] others) + { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void SearchWithParams(params object[] others) { } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index c0c83f07ef..25486902c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -9,7 +9,9 @@ using System.Reflection.Emit; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging.Abstractions; @@ -875,47 +877,107 @@ Environment.NewLine + "int b"; } [Fact] - public void ApiConventionAttributeIsNotAdded_IfModelAlreadyHasAttribute() + public void DiscoverApiConvention_DoesNotAddConventionItem_IfActionHasProducesResponseTypeAttribute() { // Arrange - var attribute = new ApiConventionAttribute(typeof(DefaultApiConventions)); - var controllerType = CreateTestControllerType(); - - var model = new ControllerModel(controllerType.GetTypeInfo(), new[] { attribute }) - { - Filters = { attribute, }, - }; + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()); + actionModel.Filters.Add(new ProducesResponseTypeAttribute(200)); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; // Act - ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model); + ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); // Assert - Assert.Collection( - model.Filters, - filter => Assert.Same(attribute, filter)); + Assert.Empty(actionModel.Properties); } [Fact] - public void ApiConventionAttributeIsAdded_IfAttributeExistsInAssembly() + public void DiscoverApiConvention_DoesNotAddConventionItem_IfActionHasProducesAttribute() { // Arrange - var controllerType = CreateTestControllerType(); - var model = new ControllerModel(controllerType.GetTypeInfo(), Array.Empty()); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()); + actionModel.Filters.Add(new ProducesAttribute(typeof(object))); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; // Act - ApiBehaviorApplicationModelProvider.AddGloballyConfiguredApiConventions(model); + ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); + + // Assert + Assert.Empty(actionModel.Properties); + } + + [Fact] + public void DiscoverApiConvention_DoesNotAddConventionItem_IfNoConventionMatches() + { + // Arrange + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.NoMatch)), + Array.Empty()); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + // Act + ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); + + // Assert + Assert.Empty(actionModel.Properties); + } + + [Fact] + public void DiscoverApiConvention_AddsConventionItem_IfConventionMatches() + { + // Arrange + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + // Act + ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); // Assert Assert.Collection( - model.Filters, - filter => Assert.IsType(filter)); + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ApiConventionResult), kvp.Key); + Assert.NotNull(kvp.Value); + }); + } + + [Fact] + public void DiscoverApiConvention_AddsConventionItem_IfActionHasNonConventionBasedFilters() + { + // Arrange + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()); + actionModel.Filters.Add(new AuthorizeFilter()); + actionModel.Filters.Add(new ServiceFilterAttribute(typeof(object))); + actionModel.Filters.Add(new ConsumesAttribute("application/xml")); + var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + // Act + ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ApiConventionResult), kvp.Key); + Assert.NotNull(kvp.Value); + }); } // A dynamically generated type in an assembly that has an ApiConventionAttribute. private static TypeBuilder CreateTestControllerType() { var attributeBuilder = new CustomAttributeBuilder( - typeof(ApiConventionAttribute).GetConstructor(new[] { typeof(Type) }), + typeof(ApiConventionTypeAttribute).GetConstructor(new[] { typeof(Type) }), new[] { typeof(DefaultApiConventions) }); var assemblyName = new AssemblyName("TestAssembly"); @@ -1202,6 +1264,13 @@ Environment.NewLine + "int b"; public IActionResult Action([ModelBinder(typeof(object))] Car car) => null; } + private class TestApiConventionController + { + public IActionResult NoMatch() => null; + + public IActionResult Delete(int id) => null; + } + private class GpsCoordinates { public long Latitude { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index a6a69bfccd..7490fe2f7f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1148,6 +1148,189 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("multipart/form-data", requestFormat.MediaType); } + [Fact] + public Task ApiConvention_ForGetMethod_ReturningModel() => ApiConvention_ForGetMethod("GetProduct"); + + [Fact] + public Task ApiConvention_ForGetMethod_ReturningTaskOfActionResultOfModel() => ApiConvention_ForGetMethod("GetTaskOfActionResultOfProduct"); + + private async Task ApiConvention_ForGetMethod(string action) + { + // Act + var response = await Client.GetStringAsync( + $"ApiExplorerResponseTypeWithApiConventionController/{action}"); + var result = JsonConvert.DeserializeObject>(response); + + // Assert + var description = Assert.Single(result); + + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(200, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(404, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + + [Fact] + public async Task ApiConvention_ForGetMethodThatDoesNotMatchConvention() + { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + + // Act + var response = await Client.GetStringAsync( + $"ApiExplorerResponseTypeWithApiConventionController/GetProducts"); + var result = JsonConvert.DeserializeObject>(response); + + // Assert + var description = Assert.Single(result); + + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(IEnumerable).FullName, responseType.ResponseType); + Assert.Equal(200, responseType.StatusCode); + var actualMediaTypes = responseType.ResponseFormats.Select(r => r.MediaType).OrderBy(r => r); + Assert.Equal(expectedMediaTypes, actualMediaTypes); + }); + } + + [Fact] + public async Task ApiConvention_ForMethodWithResponseTypeAttributes() + { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + + // Act + var response = await Client.PostAsync( + $"ApiExplorerResponseTypeWithApiConventionController/PostWithConventions", + new StringContent(string.Empty)); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(202, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(403, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + + [Fact] + public async Task ApiConvention_ForPostMethodThatMatchesConvention() + { + // Act + var response = await Client.PostAsync( + $"ApiExplorerResponseTypeWithApiConventionController/PostTaskOfProduct", + new StringContent(string.Empty)); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(201, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(400, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + + [Fact] + public async Task ApiConvention_ForPutActionThatMatchesConvention() + { + // Act + var response = await Client.PutAsync( + $"ApiExplorerResponseTypeWithApiConventionController/Put", + new StringContent(string.Empty)); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(204, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(400, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(404, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + + [Fact] + public async Task ApiConvention_ForDeleteActionThatMatchesConvention() + { + // Act + var response = await Client.DeleteAsync( + $"ApiExplorerResponseTypeWithApiConventionController/DeleteProductAsync"); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(200, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(400, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(404, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + private IEnumerable GetSortedMediaTypes(ApiExplorerResponseType apiResponseType) { return apiResponseType.ResponseFormats diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs new file mode 100644 index 0000000000..4a63fc746a --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs @@ -0,0 +1,39 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace ApiExplorerWebSite +{ + [ApiController] + [Route("ApiExplorerResponseTypeWithApiConventionController/[Action]")] + [ApiConventionType(typeof(DefaultApiConventions))] + public class ApiExplorerResponseTypeWithApiConventionController : Controller + { + [HttpGet] + public Product GetProduct(int id) => null; + + [HttpGet] + public Task> GetTaskOfActionResultOfProduct(int id) => null; + + [HttpGet] + public IEnumerable GetProducts() => null; + + [HttpPost] + [Produces("application/json")] + [ProducesResponseType(202)] + [ProducesResponseType(403)] + public IActionResult PostWithConventions() => null; + + [HttpPost] + public Task PostTaskOfProduct(Product p) => null; + + [HttpPut] + public Task Put(string id, Product product) => null; + + [HttpDelete] + public Task DeleteProductAsync(object id) => null; + } +} \ No newline at end of file From f15457c026e2c2cd7cac6030a96c0e08185b9c82 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 25 Jun 2018 11:23:45 -0700 Subject: [PATCH 073/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 154 +++++++++++++++++++-------------------- 1 file changed, 76 insertions(+), 78 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 573fdcc668..04de4b48ca 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -2,103 +2,101 @@ $(MSBuildAllProjects);$(MSBuildThisFileFullPath) - - 0.9.9 - 0.10.13 - - + + 0.9.9 + 0.10.13 2.1.0 2.1.0 2.1.0 0.42.1 2.1.0 2.1.0-rc1-final - - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 2.2.0-preview1-17090 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 1.7.0 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 2.2.0-preview1-26618-02 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 2.0.0 2.1.0 2.2.0-preview1-26618-02 - 2.2.0-preview1-34492 - 2.2.0-preview1-34492 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 15.6.1 4.7.49 2.0.3 From e1af5b8b6dc147be179e1798d33af91c982e5a4b Mon Sep 17 00:00:00 2001 From: kishanAnem Date: Tue, 26 Jun 2018 20:43:46 +0200 Subject: [PATCH 074/316] Array or List in query string does not get parsed #7712 (#7967) - exclude collections when detecting complex types in `ApiBehaviorApplicationModelProvider` - add test cases --- .../ApiBehaviorApplicationModelProvider.cs | 8 +- ...ApiBehaviorApplicationModelProviderTest.cs | 106 +++++++++++++++++- 2 files changed, 109 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 7105b9a4df..f5930f40a6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var metadata = _modelMetadataProvider.GetMetadataForProperty( controllerModel.ControllerType, property.PropertyInfo.Name); - if (metadata.IsComplexType) + if (metadata.IsComplexType && !metadata.IsCollectionType) { property.BindingInfo.BinderModelName = string.Empty; } @@ -278,9 +278,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal private bool IsComplexTypeParameter(ParameterModel parameter) { // No need for information from attributes on the parameter. Just use its type. - return _modelMetadataProvider - .GetMetadataForType(parameter.ParameterInfo.ParameterType) - .IsComplexType; + var metadata = _modelMetadataProvider + .GetMetadataForType(parameter.ParameterInfo.ParameterType); + return metadata.IsComplexType && !metadata.IsCollectionType; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index 25486902c3..fc47f2abd0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Reflection; @@ -622,6 +623,75 @@ Environment.NewLine + "int b"; Assert.Equal("gps", bindingInfo.BinderModelName); } + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnCollectionType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryOnArrayType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayType); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayTypeWithCustomName); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var controller = Assert.Single(context.Result.Controllers); + var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("ids", bindingInfo.BinderModelName); + } + [Fact] public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName() { @@ -797,6 +867,22 @@ Environment.NewLine + "int b"; Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); } + [Fact] + public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider() + { + // Arrange + var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty)); + + var provider = GetProvider(); + + // Act + provider.InferBoundPropertyModelPrefixes(controller); + + // Assert + var property = Assert.Single(controller.ControllerProperties); + Assert.Null(property.BindingInfo.BinderModelName); + } + [Fact] public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() { @@ -1124,7 +1210,7 @@ Environment.NewLine + "int b"; [HttpGet("parameter-with-model-binder-attribute")] public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null; - + [HttpGet("parameter-with-fromquery")] public IActionResult FromQuery([FromQuery] int value) => null; @@ -1137,6 +1223,15 @@ Environment.NewLine + "int b"; [HttpGet("parameter-with-fromquery-on-complextype-and-customname")] public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + [HttpGet("parameter-with-fromquery-on-collection-type")] + public IActionResult FromQueryOnCollectionType([FromQuery] ICollection value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type")] + public IActionResult FromQueryOnArrayType([FromQuery] int[] value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type-customname")] + public IActionResult FromQueryOnArrayTypeWithCustomName([FromQuery(Name = "ids")] int[] value) => null; + [HttpGet("parameter-with-fromroute")] public IActionResult FromRoute([FromRoute] int value) => null; @@ -1234,6 +1329,15 @@ Environment.NewLine + "int b"; public IActionResult SomeAction([FromQuery] TestModel test) => null; } + [ApiController] + private class ControllerWithBoundCollectionProperty + { + [FromQuery] + public List TestProperty { get; set; } + + public IActionResult SomeAction([FromQuery] List test) => null; + } + private class Car { } [ApiController] From 4dd4e5ef3e11c2f7682e56b72b739fdbf265c619 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 27 Jun 2018 12:04:08 -0700 Subject: [PATCH 075/316] Shorten names so MVC repo can be cloned on Windows benchmarks server --- Mvc.NoFun.sln | 2 +- Mvc.sln | 2 +- .../Properties/AssemblyInfo.cs | 2 +- .../AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs | 0 .../AvoidHtmlPartialAnalyzerTest.cs | 0 .../CodeAnalysisExtensionsTest.cs | 0 .../Infrastructure/MvcDiagnosticAnalyzerRunner.cs | 0 .../Infrastructure/MvcTestSource.cs | 0 .../Mvc.Analyzers.Test.csproj} | 0 .../MvcFactsTest.cs | 0 .../SymbolApiResponseMetadataProviderTest.cs | 0 ...sAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs | 0 .../DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs | 0 ...Returned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs | 0 ...nosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs | 0 ...ltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs | 0 ...reAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs | 0 ...gnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs | 0 ...reReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs | 0 .../NoDiagnosticsAreReturned_ForControllerActions.cs | 0 .../NoDiagnosticsAreReturned_ForControllerBaseActions.cs | 0 ...DiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs | 0 ...nosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs | 0 ...agnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs | 0 ...sticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs | 0 .../NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs | 0 .../DiagnosticsAreReturned_ForUseOfHtmlPartial.cs | 0 .../DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs | 0 ...sAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs | 0 .../DiagnosticsAreReturned_ForUseOfRenderPartial.cs | 0 .../DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs | 0 ...reReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs | 0 .../NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs | 0 .../NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs | 0 .../NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs | 0 .../GetAttributes_OnMethodWithoutAttributes.cs | 0 .../GetAttributes_WithMethodOverridding.cs | 0 .../CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs | 0 .../GetAttributes_WithoutMethodOverridding.cs | 0 .../HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs | 0 .../HasAttribute_ReturnsTrueForAttributesOnMethods.cs | 0 .../HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs | 0 ...asAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs | 0 .../HasAttribute_ReturnsTrueForAttributesOnProperties.cs | 0 .../HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs | 0 .../HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs | 0 .../HasAttribute_ReturnsTrueIfTypeHasAttribute.cs | 0 .../IsAssignable_ReturnsFalseForDifferentTypes.cs | 0 ...IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs | 0 .../IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs | 0 .../IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs | 0 .../IsAssignable_ReturnsTrueIfTypesAreExact.cs | 0 .../TestFiles/MvcFactsTest/IsControllerActionTests.cs | 0 .../TestFiles/MvcFactsTest/IsControllerTests.cs | 0 .../GetResponseMetadataTests.cs | 0 .../xunit.runner.json | 0 56 files changed, 3 insertions(+), 3 deletions(-) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/AvoidHtmlPartialAnalyzerTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/CodeAnalysisExtensionsTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/Infrastructure/MvcDiagnosticAnalyzerRunner.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/Infrastructure/MvcTestSource.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj => Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj} (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/MvcFactsTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/SymbolApiResponseMetadataProviderTest.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/MvcFactsTest/IsControllerActionTests.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/MvcFactsTest/IsControllerTests.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs (100%) rename test/{Microsoft.AspNetCore.Mvc.Analyzers.Test => Mvc.Analyzers.Test}/xunit.runner.json (100%) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 858650218f..2da90ff325 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -110,7 +110,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Test\Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" EndProject diff --git a/Mvc.sln b/Mvc.sln index 538cacced1..77f80bb3e4 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -160,7 +160,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorBuildWebSite.Views", " EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers", "src\Microsoft.AspNetCore.Mvc.Analyzers\Microsoft.AspNetCore.Mvc.Analyzers.csproj", "{87A3E227-C45E-4141-A59F-402908E651FD}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Test\Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj", "{E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}" EndProject diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs index 45468cf21e..9b669e0fb2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs @@ -3,4 +3,4 @@ using System.Runtime.CompilerServices; -[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Mvc.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs b/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs rename to test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs b/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs rename to test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs rename to test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs b/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs rename to test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs b/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs rename to test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj b/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/Microsoft.AspNetCore.Mvc.Analyzers.Test.csproj rename to test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs b/test/Mvc.Analyzers.Test/MvcFactsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/MvcFactsTest.cs rename to test/Mvc.Analyzers.Test/MvcFactsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs rename to test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs b/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs rename to test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs b/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs rename to test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs b/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs rename to test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs b/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs rename to test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs rename to test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Test/xunit.runner.json b/test/Mvc.Analyzers.Test/xunit.runner.json similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Analyzers.Test/xunit.runner.json rename to test/Mvc.Analyzers.Test/xunit.runner.json From 54c14b8782bc2a11528b04eb699aeca3375e2884 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 25 Jun 2018 14:24:34 -0700 Subject: [PATCH 076/316] Reacting to Routing repo's EndpointFinder changes --- .../Builder/MvcEndpointInfo.cs | 9 +- .../Internal/MvcEndpointDataSource.cs | 101 ++++- .../Routing/DispatcherUrlHelper.cs | 35 +- .../Routing/UrlHelperFactory.cs | 14 +- .../Internal/MvcEndpointDataSourceTests.cs | 384 +++++++++++++++--- .../Routing/DispatcherUrlHelperTest.cs | 72 ++-- 6 files changed, 511 insertions(+), 104 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index f88f6e4ded..bb6a9df95f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -30,7 +30,8 @@ namespace Microsoft.AspNetCore.Builder ParsedTemplate = TemplateParser.Parse(template); Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints); - Defaults = GetDefaults(ParsedTemplate, defaults); + Defaults = defaults; + MergedDefaults = GetDefaults(ParsedTemplate, defaults); } catch (Exception exception) { @@ -41,7 +42,13 @@ namespace Microsoft.AspNetCore.Builder public string Name { get; } public string Template { get; } + + // Non-inline defaults public RouteValueDictionary Defaults { get; } + + // Inline and non-inline defaults merged into one + public RouteValueDictionary MergedDefaults { get; } + public IDictionary Constraints { get; } public RouteValueDictionary DataTokens { get; } internal RouteTemplate ParsedTemplate { get; private set; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 7bce3996a5..dab81b662b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -68,6 +68,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (action.AttributeRouteInfo == null) { + // In traditional conventional routing setup, the routes defined by a user have a static order + // defined by how they are added into the list. We would like to maintain the same order when building + // up the endpoints too. + // + // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. + // This is for scenarios dealing with migrating existing Routing based code to Dispatcher world. + var conventionalRouteOrder = 0; + // Check each of the conventional templates to see if the action would be reachable // If the action and template are compatible then create an endpoint with the // area/controller/action parameter parts replaced with literals @@ -100,7 +108,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal { var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i)); - var subEndpoint = CreateEndpoint(action, subTemplate, 0, endpointInfo); + var subEndpoint = CreateEndpoint( + action, + endpointInfo.Name, + subTemplate, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo); _endpoints.Add(subEndpoint); } @@ -119,14 +133,26 @@ namespace Microsoft.AspNetCore.Mvc.Internal var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments); - var endpoint = CreateEndpoint(action, newTemplate, 0, endpointInfo); + var endpoint = CreateEndpoint( + action, + endpointInfo.Name, + newTemplate, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo); _endpoints.Add(endpoint); } } } else { - var endpoint = CreateEndpoint(action, action.AttributeRouteInfo.Template, action.AttributeRouteInfo.Order, action.AttributeRouteInfo); + var endpoint = CreateEndpoint( + action, + action.AttributeRouteInfo.Name, + action.AttributeRouteInfo.Template, + nonInlineDefaults: null, + action.AttributeRouteInfo.Order, + action.AttributeRouteInfo); _endpoints.Add(endpoint); } } @@ -144,7 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal return false; } - private bool UseDefaultValuePlusRemainingSegementsOptional(int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, RouteTemplate template) + private bool UseDefaultValuePlusRemainingSegementsOptional( + int segmentIndex, + ActionDescriptor action, + MvcEndpointInfo endpointInfo, + RouteTemplate template) { // Check whether the remaining segments are all optional and one or more of them is // for area/controller/action and has a default value @@ -164,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (IsMvcParameter(part.Name)) { - if (endpointInfo.Defaults[part.Name] is string defaultValue + if (endpointInfo.MergedDefaults[part.Name] is string defaultValue && action.RouteValues.TryGetValue(part.Name, out var routeValue) && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) { @@ -196,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } else { - if (endpointInfo.Defaults != null && string.Equals(actionValue, endpointInfo.Defaults[routeKey] as string, StringComparison.OrdinalIgnoreCase)) + if (endpointInfo.MergedDefaults != null && string.Equals(actionValue, endpointInfo.MergedDefaults[routeKey] as string, StringComparison.OrdinalIgnoreCase)) { return true; } @@ -235,7 +265,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private MatcherEndpoint CreateEndpoint(ActionDescriptor action, string template, int order, object source) + private MatcherEndpoint CreateEndpoint( + ActionDescriptor action, + string routeName, + string template, + object nonInlineDefaults, + int order, + object source) { RequestDelegate invokerDelegate = (context) => { @@ -260,10 +296,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.Add(source); metadata.Add(action); + if (!string.IsNullOrEmpty(routeName)) + { + metadata.Add(new RouteNameMetadata(routeName)); + } + // Add filter descriptors to endpoint metadata if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0) { - metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer).Select(f => f.Filter)); + metadata.AddRange(action.FilterDescriptors.OrderBy(f => f, FilterDescriptorOrderComparer.Comparer) + .Select(f => f.Filter)); } if (action.ActionConstraints != null && action.ActionConstraints.Count > 0) @@ -281,14 +323,41 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoint = new MatcherEndpoint( next => invokerDelegate, template, - action.RouteValues, + new RouteValueDictionary(nonInlineDefaults), + new RouteValueDictionary(action.RouteValues), order, metadataCollection, - action.DisplayName, - address: null); + 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; } + // Ensure required values are a subset of defaults + // Examples: + // + // Template: {controller}/{action}/{category}/{id?} + // Defaults(in-line or non in-line): category=products + // Required values: controller=foo, action=bar + // Final constructed template: foo/bar/{category}/{id?} + // Final defaults: controller=foo, action=bar, category=products + // + // Template: {controller=Home}/{action=Index}/{category=products}/{id?} + // Defaults: controller=Home, action=Index, category=products + // Required values: controller=foo, action=bar + // Final constructed template: foo/bar/{category}/{id?} + // Final defaults: controller=foo, action=bar, category=products + private void EnsureRequiredValuesInDefaults(RouteValueDictionary requiredValues, RouteValueDictionary defaults) + { + foreach (var kvp in requiredValues) + { + defaults[kvp.Key] = kvp.Value; + } + } + private IChangeToken GetCompositeChangeToken() { if (_actionDescriptorChangeProviders.Length == 1) @@ -321,5 +390,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal public override IReadOnlyList Endpoints => _endpoints; public List ConventionalEndpointInfos { get; } + + private class RouteNameMetadata : IRouteNameMetadata + { + public RouteNameMetadata(string routeName) + { + Name = routeName; + } + + public string Name { get; } + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs index bb6e1a1bdc..ea56fb5b6a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.EndpointFinders; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing @@ -15,16 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.Routing { private readonly ILogger _logger; private readonly ILinkGenerator _linkGenerator; + private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// /// Initializes a new instance of the class using the specified /// . /// /// The for the current request. + /// + /// The which finds endpoints by required route values. + /// /// The used to generate the link. /// The . public DispatcherUrlHelper( ActionContext actionContext, + IEndpointFinder routeValuesBasedEndpointFinder, ILinkGenerator linkGenerator, ILogger logger) : base(actionContext) @@ -40,6 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } _linkGenerator = linkGenerator; + _routeValuesBasedEndpointFinder = routeValuesBasedEndpointFinder; _logger = logger; } @@ -79,12 +86,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing valuesDictionary["controller"] = urlActionContext.Controller; } - var successfullyGeneratedLink = _linkGenerator.TryGetLink( - new LinkGeneratorContext() + var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( + new RouteValuesBasedEndpointFinderContext() { - SuppliedValues = valuesDictionary, + ExplicitValues = valuesDictionary, AmbientValues = AmbientValues - }, + }); + + var successfullyGeneratedLink = _linkGenerator.TryGetLink( + endpoints, + valuesDictionary, + AmbientValues, out var link); if (!successfullyGeneratedLink) @@ -107,13 +119,18 @@ namespace Microsoft.AspNetCore.Mvc.Routing var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); - var successfullyGeneratedLink = _linkGenerator.TryGetLink( - new LinkGeneratorContext() + var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( + new RouteValuesBasedEndpointFinderContext() { - Address = new Address(routeContext.RouteName), - SuppliedValues = valuesDictionary, + RouteName = routeContext.RouteName, + ExplicitValues = valuesDictionary, AmbientValues = AmbientValues - }, + }); + + var successfullyGeneratedLink = _linkGenerator.TryGetLink( + endpoints, + valuesDictionary, + AmbientValues, out var link); if (!successfullyGeneratedLink) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index ba0f7e9607..d8c257ffdc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -5,6 +5,7 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.EndpointFinders; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -49,9 +50,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing var endpointFeature = httpContext.Features.Get(); if (endpointFeature?.Endpoint != null) { - var linkGenerator = httpContext.RequestServices.GetRequiredService(); - var logger = httpContext.RequestServices.GetRequiredService>(); - urlHelper = new DispatcherUrlHelper(context, linkGenerator, logger); + var services = httpContext.RequestServices; + var linkGenerator = services.GetRequiredService(); + var routeValuesBasedEndpointFinder = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); + + urlHelper = new DispatcherUrlHelper( + context, + routeValuesBasedEndpointFinder, + linkGenerator, + logger); } else { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index f35cc05871..f3b6d778df 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matchers; @@ -20,7 +19,7 @@ using Microsoft.Extensions.Primitives; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal +namespace Microsoft.AspNetCore.Mvc.Internal { public class MvcEndpointDataSourceTests { @@ -29,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal { // Arrange var routeValue = "Value"; - var routeValues = new Dictionary + var requiredValues = new Dictionary { ["Name"] = routeValue }; @@ -43,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal { new ActionDescriptor { - RouteValues = routeValues, + RouteValues = requiredValues, DisplayName = displayName, AttributeRouteInfo = new AttributeRouteInfo { @@ -66,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal var endpoint = Assert.Single(dataSource.Endpoints); var matcherEndpoint = Assert.IsType(endpoint); - var endpointValue = matcherEndpoint.Values["Name"]; + var endpointValue = matcherEndpoint.RequiredValues["Name"]; Assert.Equal(routeValue, endpointValue); Assert.Equal(displayName, matcherEndpoint.DisplayName); @@ -187,13 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal public void InitializeEndpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List - { - CreateActionDescriptor("TestController", "TestAction") - }, 0)); - - var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); // Act @@ -218,13 +213,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal public void InitializeEndpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List - { - CreateActionDescriptor("TestController", "TestAction", "TestArea") - }, 0)); - - var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction", area = "TestArea" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); // Act @@ -243,13 +234,9 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal public void InitializeEndpoints_SingleAction_WithActionDefault() { // Arrange - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List - { - CreateActionDescriptor("TestController", "TestAction") - }, 0)); - - var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( string.Empty, "{controller}/{action}", @@ -268,15 +255,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal public void InitializeEndpoints_MultipleActions_WithActionConstraint() { // Arrange - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List - { - CreateActionDescriptor("TestController", "TestAction"), - CreateActionDescriptor("TestController", "TestAction1"), - CreateActionDescriptor("TestController", "TestAction2") - }, 0)); - - var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }, + new { controller = "TestController", action = "TestAction1" }, + new { controller = "TestController", action = "TestAction2" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( string.Empty, "{controller}/{action}", @@ -297,16 +280,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal public void InitializeEndpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List - { - CreateActionDescriptor("TestController1", "TestAction1"), - CreateActionDescriptor("TestController1", "TestAction2"), - CreateActionDescriptor("TestController1", "TestAction3"), - CreateActionDescriptor("TestController2", "TestAction1") - }, 0)); - - var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController1", action = "TestAction1" }, + new { controller = "TestController1", action = "TestAction2" }, + new { controller = "TestController1", action = "TestAction3" }, + new { controller = "TestController2", action = "TestAction1" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( string.Empty, endpointInfoRoute)); @@ -322,6 +301,276 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal Assert.Collection(dataSource.Endpoints, inspectors); } + [Fact] + public void ConventionalRoute_WithNoRouteName_DoesNotAddRouteNameMetadata() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "Home", action = "Index" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo(string.Empty, "named/{controller}/{action}/{id?}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.Null(routeNameMetadata); + } + + [Fact] + public void CanCreateMultipleEndpoints_WithSameRouteName() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "Home", action = "Index" }, + new { controller = "Products", action = "Details" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo("namedRoute", "named/{controller}/{action}/{id?}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Collection( + dataSource.Endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeNameMetadata); + Assert.Equal("namedRoute", routeNameMetadata.Name); + Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeNameMetadata); + Assert.Equal("namedRoute", routeNameMetadata.Name); + Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template); + }); + } + + [Fact] + public void InitializeEndpoints_ConventionalRoutes_StaticallyDefinedOrder_IsMaintained() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "Home", action = "Index" }, + new { controller = "Products", action = "Details" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller}/{action}/{id?}")); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: "namedRoute", + "named/{controller}/{action}/{id?}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Collection( + dataSource.Endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home/Index/{id?}", matcherEndpoint.Template); + Assert.Equal(1, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template); + Assert.Equal(2, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Products/Details/{id?}", matcherEndpoint.Template); + Assert.Equal(1, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template); + Assert.Equal(2, matcherEndpoint.Order); + }); + } + + [Fact] + public void RequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint() + { + // Arrange + var requiredValues = new RouteValueDictionary(new { area = "admin", controller = "home", action = "index" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Empty(dataSource.Endpoints); + } + + // Since area, controller, action and page are special, check to see if the followin test succeeds for a + // custom required value too. + [Fact(Skip = "Needs review")] + public void NonReservedRequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint() + { + // Arrange + var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index", foo = "bar" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Empty(dataSource.Endpoints); + } + + [Fact] + public void TemplateParameter_WithNoDefaultOrRequiredValue_DoesNotProduceEndpoint() + { + // Arrange + var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Empty(dataSource.Endpoints); + } + + [Fact] + public void TemplateParameter_WithDefaultValue_AndNullRequiredValue_DoesNotProduceEndpoint() + { + // Arrange + var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area=admin}/{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Empty(dataSource.Endpoints); + } + + [Fact] + public void TemplateParameter_WithNullRequiredValue_DoesNotProduceEndpoint() + { + // Arrange + var requiredValues = new RouteValueDictionary(new { area = (string)null, controller = "home", action = "index" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + Assert.Empty(dataSource.Endpoints); + } + + [Fact] + public void NoDefaultValues_RequiredValues_UsedToCreateDefaultValues() + { + // Arrange + var expectedDefaults = new RouteValueDictionary(new { controller = "Foo", action = "Bar" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: expectedDefaults); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Foo/Bar", matcherEndpoint.Template); + AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + } + + [Fact] + public void RequiredValues_NotPresent_InDefaultValues_IsAddedToDefaultValues() + { + // Arrange + var requiredValues = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", subarea = "test" }); + var expectedDefaults = requiredValues; + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Foo/Bar", matcherEndpoint.Template); + AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + } + + [Fact] + public void RequiredValues_IsSubsetOf_DefaultValues() + { + // Arrange + var requiredValues = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", subarea = "test" }); + var expectedDefaults = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", subarea = "test", subscription = "general" }); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}/{subscription=general}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.Template); + AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + } + + [Fact] + public void RequiredValues_HavingNull_AndNotPresentInDefaultValues_IsAddedToDefaultValues() + { + // Arrange + var requiredValues = new RouteValueDictionary( + new { area = (string)null, controller = "Foo", action = "Bar", page = (string)null }); + var expectedDefaults = requiredValues; + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); + + // Act + dataSource.InitializeEndpoints(); + + // Assert + var endpoint = Assert.Single(dataSource.Endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Foo/Bar", matcherEndpoint.Template); + AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + } + private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null, @@ -362,18 +611,45 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver); } + private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues) + { + var actionDescriptors = new List(); + foreach (var requiredValue in requiredValues) + { + actionDescriptors.Add(CreateActionDescriptor(requiredValue)); + } + + var actionDescriptorCollectionProvider = new Mock(); + actionDescriptorCollectionProvider + .Setup(m => m.ActionDescriptors) + .Returns(new ActionDescriptorCollection(actionDescriptors, version: 0)); + return actionDescriptorCollectionProvider.Object; + } + private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) { - return new ActionDescriptor + return CreateActionDescriptor(new { controller = controller, action = action, area = area }); + } + + private ActionDescriptor CreateActionDescriptor(object requiredValues) + { + var actionDescriptor = new ActionDescriptor(); + var routeValues = new RouteValueDictionary(requiredValues); + foreach (var kvp in routeValues) { - RouteValues = - { - ["controller"] = controller, - ["action"] = action, - ["area"] = area - }, - DisplayName = string.Empty, - }; + actionDescriptor.RouteValues[kvp.Key] = kvp.Value?.ToString(); + } + return actionDescriptor; + } + + private void AssertIsSubset(RouteValueDictionary subset, RouteValueDictionary fullSet) + { + foreach (var subsetPair in subset) + { + var isPresent = fullSet.TryGetValue(subsetPair.Key, out var fullSetPairValue); + Assert.True(isPresent); + Assert.Equal(subsetPair.Value, fullSetPairValue); + } } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs index 6c42555191..5d52f760a7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs @@ -36,12 +36,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing endpoints.Add(new MatcherEndpoint( next => httpContext => Task.CompletedTask, template, - null, + new RouteValueDictionary(), + new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, - null, - new Address(routeName) - )); + null)); return CreateUrlHelper(endpoints, appRoot, host, protocol); } @@ -53,10 +52,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing Endpoint = new MatcherEndpoint( next => cntxt => Task.CompletedTask, "/", - new { }, + new RouteValueDictionary(), + new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, - null, null) }); @@ -79,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing string template, object defaults) { - var endpoint = GetEndpoint(routeName, template, defaults); + var endpoint = GetEndpoint(routeName, template, new RouteValueDictionary(defaults)); var services = CreateServices(new[] { endpoint }); var httpContext = CreateHttpContext(services, appRoot: "", host: null, protocol: null); var actionContext = CreateActionContext(httpContext); @@ -101,25 +100,36 @@ namespace Microsoft.AspNetCore.Mvc.Routing private List GetDefaultEndpoints() { var endpoints = new List(); - endpoints.Add(new MatcherEndpoint( - next => (httpContext) => Task.CompletedTask, - "{controller}/{action}/{id}", - new { id = "defaultid" }, - 0, - EndpointMetadataCollection.Empty, - "RouteWithNoName", - address: null)); - endpoints.Add(new MatcherEndpoint( - next => (httpContext) => Task.CompletedTask, - "named/{controller}/{action}/{id}", - new { id = "defaultid" }, - 0, - EndpointMetadataCollection.Empty, - "RouteWithNoName", - new Address("namedroute"))); + endpoints.Add(CreateEndpoint(null, "home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 1)); + endpoints.Add(CreateEndpoint(null, "home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 2)); + endpoints.Add(CreateEndpoint(null, "home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3)); + endpoints.Add(CreateEndpoint(null, "home2/contact/{id?}", new { id = "defaultid", controller = "home2", action = "contact" }, 4)); + endpoints.Add(CreateEndpoint(null, "home3/contact/{id?}", new { id = "defaultid", controller = "home3", action = "contact" }, 5)); + endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 6)); + endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7)); + endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 8)); + endpoints.Add(CreateEndpoint("MyRouteName", "any/url", new { }, 9)); return endpoints; } + private MatcherEndpoint CreateEndpoint(string routeName, string template, object defaults, int order) + { + var metadata = EndpointMetadataCollection.Empty; + if (!string.IsNullOrEmpty(routeName)) + { + metadata = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + } + + return new MatcherEndpoint( + next => (httpContext) => Task.CompletedTask, + template, + new RouteValueDictionary(defaults), + new RouteValueDictionary(), + order, + metadata, + "DisplayName"); + } + private IServiceProvider CreateServices(IEnumerable endpoints) { if (endpoints == null) @@ -135,16 +145,26 @@ namespace Microsoft.AspNetCore.Mvc.Routing return services.BuildServiceProvider(); } - private MatcherEndpoint GetEndpoint(string name, string template, object defaults) + private MatcherEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults) { return new MatcherEndpoint( next => c => Task.CompletedTask, template, defaults, + new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, - null, - new Address(name)); + null); + } + + private class RouteNameMetadata : IRouteNameMetadata + { + public RouteNameMetadata(string routeName) + { + Name = routeName; + } + + public string Name { get; } } } } From 13585f711f942dd22e2d28654fbde388ccb6f3f2 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 28 Jun 2018 13:27:15 -0700 Subject: [PATCH 077/316] Using Routing feature branch package --- build/dependencies.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 04de4b48ca..b1df5c0dd0 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -39,7 +39,7 @@ 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-a-preview1-react-to-routing-finder-changes-16711 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 @@ -48,8 +48,8 @@ 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-a-preview1-routevaluebased-endpoint-finder-16705 + 2.2.0-a-preview1-routevaluebased-endpoint-finder-16705 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 From 192e8073ee74cff5756f1548955881562b0527b5 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 28 Jun 2018 15:06:48 -0700 Subject: [PATCH 078/316] Work around `CryptographicException`s thrown in some full framework test runs - always skip affected tests on full framework --- .../BasicApiTest.cs | 20 +++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs index 42d2a5a5ce..289eb16f92 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs @@ -5,6 +5,7 @@ using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Net.Http.Headers; using Xunit; @@ -56,7 +57,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } - [Fact] + // Tests are conditional to avoid occasional CI failures on Windows 8 and Windows 2012 under full framework. + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] public async Task Token_WithKnownUser_ReturnsOkAndToken() { // Arrange & Act @@ -81,7 +84,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.Unauthorized, response.StatusCode); } - [Theory] + [ConditionalTheory] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] [InlineData("reader@example.com")] [InlineData("writer@example.com")] public async Task FindByStatus_WithToken_ReturnsOkAndPet(string username) @@ -108,7 +112,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.NotEmpty(json); } - [Fact] + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] public async Task FindById_WithInvalidPetId_ReturnsNotFound() { // Arrange & Act 1 @@ -128,7 +133,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); } - [Fact] + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] public async Task FindById_WithValidPetId_ReturnsOkAndPet() { // Arrange & Act 1 @@ -153,7 +159,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.NotEmpty(json); } - [Fact] + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] public async Task AddPet_WithInsufficientClaims_ReturnsForbidden() { // Arrange & Act 1 @@ -185,7 +192,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.Forbidden, response.StatusCode); } - [Fact] + [ConditionalFact] + [FrameworkSkipCondition(RuntimeFrameworks.CLR, SkipReason = "See aspnet/Identity#1630")] public async Task AddPet_WithValidClaims_ReturnsCreated() { // Arrange & Act 1 From 0295f6d6e31c1b60aacc4bfffe0c0b934e2fb9d2 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 29 Jun 2018 04:39:32 -0700 Subject: [PATCH 079/316] Revert "Using Routing feature branch package" This reverts commit 13585f711f942dd22e2d28654fbde388ccb6f3f2. --- build/dependencies.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b1df5c0dd0..04de4b48ca 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -39,7 +39,7 @@ 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 - 2.2.0-a-preview1-react-to-routing-finder-changes-16711 + 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 @@ -48,8 +48,8 @@ 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 - 2.2.0-a-preview1-routevaluebased-endpoint-finder-16705 - 2.2.0-a-preview1-routevaluebased-endpoint-finder-16705 + 2.2.0-preview1-34530 + 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 2.2.0-preview1-34530 From 66cb3d50aa64bb8efe884986f7290e0b9deeac3e Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 29 Jun 2018 04:48:28 -0700 Subject: [PATCH 080/316] Upgraded dependencies.props --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 04de4b48ca..3744df253b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 2.2.0-preview1-17090 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 1.7.0 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 2.2.0-preview1-26618-02 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 2.0.0 2.1.0 2.2.0-preview1-26618-02 - 2.2.0-preview1-34530 - 2.2.0-preview1-34530 + 2.2.0-preview1-34576 + 2.2.0-preview1-34576 15.6.1 4.7.49 2.0.3 From 224017c76223505e22b44f996d7cd8190c3fa0a3 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Thu, 28 Jun 2018 14:49:37 -0700 Subject: [PATCH 081/316] Update CI and build tools for to use 2.2 versions --- .vsts-pipelines/builds/ci-internal.yml | 4 ++-- .vsts-pipelines/builds/ci-public.yml | 6 +++--- benchmarkapps/BasicApi/benchmarks.json | 10 +++++----- benchmarkapps/BasicViews/benchmarks.json | 8 ++++---- build/repo.props | 1 + korebuild.json | 4 ++-- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml index d7ceb76378..dc7b8a3cb9 100644 --- a/.vsts-pipelines/builds/ci-internal.yml +++ b/.vsts-pipelines/builds/ci-internal.yml @@ -1,5 +1,5 @@ trigger: -- dev +- master - release/* resources: @@ -7,7 +7,7 @@ resources: - repository: buildtools type: git name: aspnet-BuildTools - ref: refs/heads/dev + ref: refs/heads/release/2.2 phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml index b7f25723f8..f5087d9c30 100644 --- a/.vsts-pipelines/builds/ci-public.yml +++ b/.vsts-pipelines/builds/ci-public.yml @@ -1,5 +1,5 @@ trigger: -- dev +- master - release/* # See https://github.com/aspnet/BuildTools @@ -9,7 +9,7 @@ resources: type: github endpoint: DotNet-Bot GitHub Connection name: aspnet/BuildTools - ref: refs/heads/dev - + ref: refs/heads/release/2.2 + phases: - template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/benchmarkapps/BasicApi/benchmarks.json b/benchmarkapps/BasicApi/benchmarks.json index 4a99d0bbd3..7b64057987 100644 --- a/benchmarkapps/BasicApi/benchmarks.json +++ b/benchmarkapps/BasicApi/benchmarks.json @@ -7,7 +7,7 @@ "PresetHeaders": "Json", "ReadyStateText": "Application started.", "Source": { - "BranchOrCommit": "dev", + "BranchOrCommit": "release/2.2", "Project": "benchmarkapps/BasicApi/BasicApi.csproj", "Repository": "https://github.com/aspnet/mvc.git" } @@ -19,20 +19,20 @@ }, "BasicApi.GetUsingQueryString": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua" }, "Path": "/pet/findByStatus", "Query": "?status=available" }, "BasicApi.GetUsingRouteValue": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua" }, "Path": "/pet/-1" }, "BasicApi.GetUsingRouteValueWithoutAuthorization": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/getWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/getWithToken.lua" }, "Path": "/pet/anonymous/-1" }, @@ -41,7 +41,7 @@ }, "BasicApi.Post": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicApi/postJsonWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua" }, "Path": "/pet" } diff --git a/benchmarkapps/BasicViews/benchmarks.json b/benchmarkapps/BasicViews/benchmarks.json index 82e0812a21..6a50d9386d 100644 --- a/benchmarkapps/BasicViews/benchmarks.json +++ b/benchmarkapps/BasicViews/benchmarks.json @@ -7,7 +7,7 @@ "PresetHeaders": "Html", "ReadyStateText": "Application started.", "Source": { - "BranchOrCommit": "dev", + "BranchOrCommit": "release/2.2", "Project": "benchmarkapps/BasicViews/BasicViews.csproj", "Repository": "https://github.com/aspnet/mvc.git" } @@ -20,19 +20,19 @@ }, "BasicViews.Post": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/postWithToken.lua" }, "Path": "/Home/Index" }, "BasicViews.PostIgnoringToken": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/postWithToken.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/postWithToken.lua" }, "Path": "/Home/IndexWithoutToken" }, "BasicViews.PostWithoutToken": { "ClientProperties": { - "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/dev/benchmarkapps/BasicViews/post.lua" + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicViews/post.lua" }, "Path": "/Home/IndexWithoutToken" } diff --git a/build/repo.props b/build/repo.props index 8156b45eaf..3bd17f1b84 100644 --- a/build/repo.props +++ b/build/repo.props @@ -14,6 +14,7 @@ Internal.AspNetCore.Universe.Lineup + 2.2.0-* https://dotnet.myget.org/F/aspnetcore-dev/api/v3/index.json diff --git a/korebuild.json b/korebuild.json index bd5d51a51b..d217d06e3e 100644 --- a/korebuild.json +++ b/korebuild.json @@ -1,4 +1,4 @@ { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev" + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json", + "channel": "release/2.2" } From 7f2a64e32beb9d08cfcb5e2158605ce84a886c95 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 29 Jun 2018 09:41:41 -0700 Subject: [PATCH 082/316] Razor runtime compilation produces errors if running on a shared runtime that's rolled forward Do not provide compilation references from runtime MVC assemblies. This avoids cases where the app is compiled against an older MVC but running against a newer one (e.g. shared fx roll forward) resulting in compiling against multiple versions of MVC assemblies Fixes #7969 --- .../MvcServiceCollectionExtensions.cs | 23 +++++++++++++++---- .../Controllers/HomeController.cs | 2 +- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs index b7baee8634..037bcdd2d3 100644 --- a/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs @@ -2,6 +2,8 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using Microsoft.AspNetCore.Mvc; @@ -59,15 +61,15 @@ namespace Microsoft.Extensions.DependencyInjection private static void AddDefaultFrameworkParts(ApplicationPartManager partManager) { var mvcTagHelpersAssembly = typeof(InputTagHelper).GetTypeInfo().Assembly; - if(!partManager.ApplicationParts.OfType().Any(p => p.Assembly == mvcTagHelpersAssembly)) + if (!partManager.ApplicationParts.OfType().Any(p => p.Assembly == mvcTagHelpersAssembly)) { - partManager.ApplicationParts.Add(new AssemblyPart(mvcTagHelpersAssembly)); + partManager.ApplicationParts.Add(new FrameworkAssemblyPart(mvcTagHelpersAssembly)); } - + var mvcRazorAssembly = typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly; - if(!partManager.ApplicationParts.OfType().Any(p => p.Assembly == mvcRazorAssembly)) + if (!partManager.ApplicationParts.OfType().Any(p => p.Assembly == mvcRazorAssembly)) { - partManager.ApplicationParts.Add(new AssemblyPart(mvcRazorAssembly)); + partManager.ApplicationParts.Add(new FrameworkAssemblyPart(mvcRazorAssembly)); } } @@ -94,5 +96,16 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + + [DebuggerDisplay("{Name}")] + private class FrameworkAssemblyPart : AssemblyPart, ICompilationReferencesProvider + { + public FrameworkAssemblyPart(Assembly assembly) + : base(assembly) + { + } + + IEnumerable ICompilationReferencesProvider.GetReferencePaths() => Enumerable.Empty(); + } } } diff --git a/test/WebSites/BasicWebSite/Controllers/HomeController.cs b/test/WebSites/BasicWebSite/Controllers/HomeController.cs index 454aca34b8..5d5fb11525 100644 --- a/test/WebSites/BasicWebSite/Controllers/HomeController.cs +++ b/test/WebSites/BasicWebSite/Controllers/HomeController.cs @@ -129,7 +129,7 @@ namespace BasicWebSite.Controllers // Ensures that the entry assembly part is marked correctly. var assemblyPartMetadata = applicationPartManager .ApplicationParts - .Where(part => part.GetType() == typeof(AssemblyPart)) + .OfType() .Select(part => part.Name) .ToArray(); From 133d49c57eefadfd22d6c1c2820612c3ba4542bd Mon Sep 17 00:00:00 2001 From: Nathanael Marchand Date: Tue, 5 Jun 2018 14:24:42 +0200 Subject: [PATCH 083/316] Fix Api Explorer not returning type with ActionResult and no type in ProducesResponseTypeAttribute --- .../ApiResponseTypeProvider.cs | 13 +- .../ApiResponseTypeProviderTest.cs | 6 +- .../DefaultApiDescriptionProviderTest.cs | 210 ++++++++++++++++++ .../ApiExplorerTest.cs | 10 +- 4 files changed, 233 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index e0953ca6e2..d6c7cd7661 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -86,13 +86,24 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { metadataAttribute.SetContentTypes(contentTypes); - if (metadataAttribute.Type != null) + if (metadataAttribute.Type == typeof(void) && + type != null && + (metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created)) + { + // ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified. + // In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a + // [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred + // from the return type. + objectTypes[metadataAttribute.StatusCode] = type; + } + else if (metadataAttribute.Type != null) { objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; } } } + // Set the default status only when no status has already been set explicitly if (objectTypes.Count == 0 && type != null) { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 99e862dcf4..7f3247deac 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -98,9 +98,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer responseType => { Assert.Equal(200, responseType.StatusCode); - Assert.Equal(typeof(void), responseType.Type); + Assert.Equal(typeof(BaseModel), responseType.Type); Assert.False(responseType.IsDefaultResponse); - Assert.Empty(responseType.ApiResponseFormats); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); }, responseType => { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index b661be6034..5caceeaa02 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -665,6 +665,216 @@ namespace Microsoft.AspNetCore.Mvc.Description }); } + [Theory] + [InlineData(nameof(ReturnsActionResultOfProduct))] + [InlineData(nameof(ReturnsTaskOfActionResultOfProduct))] + public void GetApiDescription_ReturnsActionResultOfTWithProducesContentType( + string methodName) + { + // Arrange + var action = CreateActionDescriptor(methodName); + action.FilterDescriptors = new List() + { + // Since action is returning Void or Task, it does not make sense to provide a value for the + // 'Type' property to ProducesAttribute. But the same action could return other types of data + // based on runtime conditions. + new FilterDescriptor( + new ProducesAttribute("text/json", "application/json"), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(200), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(202), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(BadData), 400), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500), + FilterScope.Action) + }; + var expectedMediaTypes = new[] { "application/json", "text/json" }; + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.SupportedResponseTypes.Count); + + Assert.Collection( + description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), + responseType => + { + Assert.Equal(typeof(Product), responseType.Type); + Assert.Equal(200, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(void), responseType.Type); + Assert.Equal(202, responseType.StatusCode); + Assert.Null(responseType.ModelMetadata); + Assert.Empty(GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(BadData), responseType.Type); + Assert.Equal(400, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(ErrorDetails), responseType.Type); + Assert.Equal(500, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }); + } + + [Theory] + [InlineData(nameof(ReturnsActionResultOfProduct))] + [InlineData(nameof(ReturnsTaskOfActionResultOfProduct))] + public void GetApiDescription_ReturnsActionResultOfTWithProducesContentType_ForStatusCode201( + string methodName) + { + // Arrange + var action = CreateActionDescriptor(methodName); + action.FilterDescriptors = new List() + { + // Since action is returning Void or Task, it does not make sense to provide a value for the + // 'Type' property to ProducesAttribute. But the same action could return other types of data + // based on runtime conditions. + new FilterDescriptor( + new ProducesAttribute("text/json", "application/json"), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(201), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(204), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(BadData), 400), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500), + FilterScope.Action) + }; + var expectedMediaTypes = new[] { "application/json", "text/json" }; + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.SupportedResponseTypes.Count); + + Assert.Collection( + description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), + responseType => + { + Assert.Equal(typeof(Product), responseType.Type); + Assert.Equal(201, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(void), responseType.Type); + Assert.Equal(204, responseType.StatusCode); + Assert.Null(responseType.ModelMetadata); + Assert.Empty(GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(BadData), responseType.Type); + Assert.Equal(400, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(ErrorDetails), responseType.Type); + Assert.Equal(500, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }); + } + + [Theory] + [InlineData(nameof(ReturnsActionResultOfSequenceOfProducts))] + [InlineData(nameof(ReturnsTaskOfActionResultOfSequenceOfProducts))] + public void GetApiDescription_ReturnsActionResultOfSequenceOfTWithProducesContentType( + string methodName) + { + // Arrange + var action = CreateActionDescriptor(methodName); + action.FilterDescriptors = new List() + { + // Since action is returning Void or Task, it does not make sense to provide a value for the + // 'Type' property to ProducesAttribute. But the same action could return other types of data + // based on runtime conditions. + new FilterDescriptor( + new ProducesAttribute("text/json", "application/json"), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(200), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(201), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(BadData), 400), + FilterScope.Action), + new FilterDescriptor( + new ProducesResponseTypeAttribute(typeof(ErrorDetails), 500), + FilterScope.Action) + }; + var expectedMediaTypes = new[] { "application/json", "text/json" }; + + // Act + var descriptions = GetApiDescriptions(action); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal(4, description.SupportedResponseTypes.Count); + + Assert.Collection( + description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), + responseType => + { + Assert.Equal(typeof(IEnumerable), responseType.Type); + Assert.Equal(200, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(IEnumerable), responseType.Type); + Assert.Equal(201, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(BadData), responseType.Type); + Assert.Equal(400, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }, + responseType => + { + Assert.Equal(typeof(ErrorDetails), responseType.Type); + Assert.Equal(500, responseType.StatusCode); + Assert.NotNull(responseType.ModelMetadata); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }); + } + [Theory] [InlineData(nameof(ReturnsVoid))] [InlineData(nameof(ReturnsTask))] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 7490fe2f7f..e575967651 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using ApiExplorerWebSite; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Testing.xunit; @@ -1156,6 +1157,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests private async Task ApiConvention_ForGetMethod(string action) { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + // Act var response = await Client.GetStringAsync( $"ApiExplorerResponseTypeWithApiConventionController/{action}"); @@ -1168,9 +1172,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(Product).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }, responseType => { @@ -1198,7 +1202,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => { - Assert.Equal(typeof(IEnumerable).FullName, responseType.ResponseType); + Assert.Equal(typeof(IEnumerable).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode); var actualMediaTypes = responseType.ResponseFormats.Select(r => r.MediaType).OrderBy(r => r); Assert.Equal(expectedMediaTypes, actualMediaTypes); From f6befb9ed333b0a76b24b8c438c181e08b1ad0ff Mon Sep 17 00:00:00 2001 From: Zbginiew Dobras Date: Mon, 25 Jun 2018 11:26:56 +0200 Subject: [PATCH 084/316] Added ObjectResult implementation for Unauthorized response --- .../ControllerBase.cs | 8 ++++++ .../UnauthorizedObjectResult.cs | 25 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs index 5626f1ccd2..c51f809da2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs @@ -1704,6 +1704,14 @@ namespace Microsoft.AspNetCore.Mvc public virtual UnauthorizedResult Unauthorized() => new UnauthorizedResult(); + /// + /// Creates an that produces a response. + /// + /// The created for the response. + [NonAction] + public virtual UnauthorizedObjectResult Unauthorized(object value) + => new UnauthorizedObjectResult(value); + /// /// Creates an that produces a response. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs new file mode 100644 index 0000000000..ebab2df60f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs @@ -0,0 +1,25 @@ +// 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.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// An that when executed will produce a Unauthorized (401) response. + /// + [DefaultStatusCode(DefaultStatusCode)] + public class UnauthorizedObjectResult : ObjectResult + { + private const int DefaultStatusCode = StatusCodes.Status401Unauthorized; + + /// + /// Creates a new instance. + /// + public UnauthorizedObjectResult(object value) : base(value) + { + StatusCode = DefaultStatusCode; + } + } +} \ No newline at end of file From c2fcfabdf3f7bbcaaecbc77ecbfe358b3b7e0092 Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Sat, 30 Jun 2018 01:02:38 +0200 Subject: [PATCH 085/316] Add optional property to PartialTagHelper (#7991) * Add optional to PartialTagHelper Addresses #7268 --- .../PartialTagHelper.cs | 39 +++++++++++----- .../PartialTagHelperTest.cs | 44 ++++++++++++++++++- 2 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs index 1a463966cf..fef6b7c0e8 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { private const string ForAttributeName = "for"; private const string ModelAttributeName = "model"; + private const string OptionalAttributeName = "optional"; private object _model; private bool _hasModel; private bool _hasFor; @@ -74,6 +75,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } } + /// + /// When optional, executing the tag helper will no-op if the view cannot be located. + /// Otherwise will throw stating the view could not be found. + /// + [HtmlAttributeName(OptionalAttributeName)] + public bool Optional { get; set; } + /// /// A to pass into the partial view. /// @@ -96,16 +104,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers throw new ArgumentNullException(nameof(context)); } - var model = ResolveModel(); - var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize); - using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8)) - { - await RenderPartialViewAsync(writer, model); + var viewEngineResult = FindView(); - // Reset the TagName. We don't want `partial` to render. - output.TagName = null; - output.Content.SetHtmlContent(viewBuffer); + if (viewEngineResult.Success) + { + var model = ResolveModel(); + var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize); + using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8)) + { + await RenderPartialViewAsync(writer, model, viewEngineResult.View); + output.Content.SetHtmlContent(viewBuffer); + } } + + // Reset the TagName. We don't want `partial` to render. + output.TagName = null; } // Internal for testing @@ -139,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return ViewContext.ViewData.Model; } - private async Task RenderPartialViewAsync(TextWriter writer, object model) + private ViewEngineResult FindView() { var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false); var getViewLocations = viewEngineResult.SearchedLocations; @@ -148,7 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false); } - if (!viewEngineResult.Success) + if (!viewEngineResult.Success && !Optional) { var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations); var locations = string.Empty; @@ -161,7 +174,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Resources.FormatViewEngine_PartialViewNotFound(Name, locations)); } - var view = viewEngineResult.View; + return viewEngineResult; + } + + private async Task RenderPartialViewAsync(TextWriter writer, object model, IView view) + { // Determine which ViewData we should use to construct a new ViewData var baseViewData = ViewData ?? ViewContext.ViewData; var newViewData = new ViewDataDictionary(baseViewData, model); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs index 21ac445239..21edd86f7d 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs @@ -569,7 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Fact] - public async Task ProcessAsync_Throws_IfGetViewAndFindReturnNotFoundResults() + public async Task ProcessAsync_Throws_If_NotOptional_And_GetViewAndFindReturnNotFoundResults() { // Arrange var bufferScope = new TestViewBufferScope(); @@ -596,6 +596,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Name = partialName, ViewContext = viewContext, ViewData = viewData, + Optional = false }; var tagHelperContext = GetTagHelperContext(); var output = GetTagHelperOutput(); @@ -605,6 +606,47 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers () => tagHelper.ProcessAsync(tagHelperContext, output)); Assert.Equal(expected, exception.Message); } + + [Fact] + public async Task ProcessAsync_IfOptional_And_ViewIsNotFound_WillNotRenderAnything() + { + // Arrange + var expected = string.Empty; + var bufferScope = new TestViewBufferScope(); + var partialName = "_ThisViewDoesNotExists"; + var model = new object(); + var viewContext = GetViewContext(); + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + v.Writer.Write(expected); + }) + .Returns(Task.CompletedTask); + + var viewEngine = new Mock(); + viewEngine.Setup(v => v.GetView(It.IsAny(), partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty())); + viewEngine.Setup(v => v.FindView(viewContext, partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, searchedLocations: Array.Empty())); + + var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope) + { + Name = partialName, + ViewContext = viewContext, + Optional = true + }; + var tagHelperContext = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act + await tagHelper.ProcessAsync(tagHelperContext, output); + + // Assert + var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder()); + Assert.Empty(content); + } private static ViewContext GetViewContext() { From 335500ab0ed76e073514130068aab43a40ebba3e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 26 Jun 2018 12:34:04 -0700 Subject: [PATCH 086/316] Use ModelMetadata from actual types for validation Fixes https://github.com/aspnet/Mvc/issues/7952 --- .../Metadata/ModelMetadataIdentity.cs | 22 +- .../ModelBinding/ModelMetadataProvider.cs | 22 ++ .../ControllerBoundPropertyDescriptor.cs | 5 +- .../ControllerParameterDescriptor.cs | 5 +- .../IParameterInfoParameterDescriptor.cs | 19 ++ .../IPropertyInfoParameterDescriptor.cs | 19 ++ .../Metadata/DefaultModelMetadataProvider.cs | 113 ++++++-- .../ModelBinding/Metadata/ModelAttributes.cs | 61 +++- .../ModelBinding/ParameterBinder.cs | 38 +++ .../HandlerParameterDescriptor.cs | 6 +- .../PageBoundPropertyDescriptor.cs | 8 +- .../DefaultModelMetadataProviderTest.cs | 181 ++++++++++++ .../Metadata/ModelAttributesTest.cs | 58 +++- .../ModelBinding/ParameterBinderTest.cs | 263 ++++++++++++++++++ .../BasicTests.cs | 3 +- .../Infrastructure/HttpClientExtensions.cs | 3 +- .../InputFormatterTests.cs | 98 ++++++- .../RazorPagesTest.cs | 60 ++++ test/WebSites/BasicWebSite/Startup.cs | 3 +- .../PolymorhpicPropertyBindingController.cs | 25 ++ .../PolymorphicBindingController.cs | 32 +++ .../FormatterWebSite/IModelConverter.cs | 30 ++ .../FormatterWebSite/Models/BaseModel.cs | 10 + .../FormatterWebSite/Models/DerivedModel.cs | 14 + .../FormatterWebSite/Models/IModel.cs | 9 + .../FormatterWebSite/PolymorphicBinder.cs | 24 ++ test/WebSites/FormatterWebSite/Startup.cs | 3 + .../RazorPagesWebSite/Models/IUserModel.cs | 9 + .../RazorPagesWebSite/Models/UserModel.cs | 7 +- .../PropertyBinding/PolymorphicBinding.cs | 24 ++ .../PropertyBinding/PolymorphicBinding.cshtml | 6 + .../PolymorphicModelBinder.cs | 32 +++ 32 files changed, 1167 insertions(+), 45 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs create mode 100644 test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs create mode 100644 test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs create mode 100644 test/WebSites/FormatterWebSite/IModelConverter.cs create mode 100644 test/WebSites/FormatterWebSite/Models/BaseModel.cs create mode 100644 test/WebSites/FormatterWebSite/Models/DerivedModel.cs create mode 100644 test/WebSites/FormatterWebSite/Models/IModel.cs create mode 100644 test/WebSites/FormatterWebSite/PolymorphicBinder.cs create mode 100644 test/WebSites/RazorPagesWebSite/Models/IUserModel.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs index 782b84b2cd..af97d9a043 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -66,17 +66,37 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata }; } + /// + /// Creates a for the provided parameter. + /// + /// The . + /// A . public static ModelMetadataIdentity ForParameter(ParameterInfo parameter) + => ForParameter(parameter, parameter?.ParameterType); + + /// + /// Creates a for the provided parameter with the specified + /// model type. + /// + /// The . + /// The model type. + /// A . + public static ModelMetadataIdentity ForParameter(ParameterInfo parameter, Type modelType) { if (parameter == null) { throw new ArgumentNullException(nameof(parameter)); } + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + return new ModelMetadataIdentity() { Name = parameter.Name, - ModelType = parameter.ParameterType, + ModelType = modelType, ParameterInfo = parameter, }; } diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs index 1b4f01cd6d..147ccc45f5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs @@ -32,5 +32,27 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// The . /// A instance describing the . public abstract ModelMetadata GetMetadataForParameter(ParameterInfo parameter); + + /// + /// Supplies metadata describing a parameter. + /// + /// The + /// The actual model type. + /// A instance describing the . + public virtual ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType) + { + throw new NotSupportedException(); + } + + /// + /// Supplies metadata describing a property. + /// + /// The . + /// The actual model type. + /// A instance describing the . + public virtual ModelMetadata GetMetadataForProperty(PropertyInfo propertyInfo, Type modelType) + { + throw new NotSupportedException(); + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs index 7c41dda166..709dffcf5c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs @@ -3,16 +3,17 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.Controllers { /// /// A descriptor for model bound properties of a controller. /// - public class ControllerBoundPropertyDescriptor : ParameterDescriptor + public class ControllerBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor { /// - /// Gets or sets the for this property. + /// Gets or sets the for this property. /// public PropertyInfo PropertyInfo { get; set; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs index e48ef9dd00..593a40d0fc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs @@ -3,16 +3,17 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.Controllers { /// /// A descriptor for method parameters of an action method. /// - public class ControllerParameterDescriptor : ParameterDescriptor + public class ControllerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor { /// - /// Gets or sets the . + /// Gets or sets the . /// public ParameterInfo ParameterInfo { get; set; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs new file mode 100644 index 0000000000..93e6a09b28 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs @@ -0,0 +1,19 @@ +// 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.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// A for action parameters. + /// + public interface IParameterInfoParameterDescriptor + { + /// + /// Gets the . + /// + ParameterInfo ParameterInfo { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs new file mode 100644 index 0000000000..5a9a8682d1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs @@ -0,0 +1,19 @@ +// 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.Reflection; +using Microsoft.AspNetCore.Mvc.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// A for bound properties. + /// + public interface IPropertyInfoParameterDescriptor + { + /// + /// Gets the . + /// + PropertyInfo PropertyInfo { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs index 5b79143002..b9e374701e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.Reflection; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; @@ -97,14 +98,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata return cacheEntry.Details.Properties; } + /// public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter) + => GetMetadataForParameter(parameter, parameter?.ParameterType); + + /// + public override ModelMetadata GetMetadataForParameter(ParameterInfo parameter, Type modelType) { if (parameter == null) { throw new ArgumentNullException(nameof(parameter)); } - var cacheEntry = GetCacheEntry(parameter); + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var cacheEntry = GetCacheEntry(parameter, modelType); return cacheEntry.Metadata; } @@ -122,6 +133,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata return cacheEntry.Metadata; } + /// + public override ModelMetadata GetMetadataForProperty(PropertyInfo propertyInfo, Type modelType) + { + if (propertyInfo == null) + { + throw new ArgumentNullException(nameof(propertyInfo)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + var cacheEntry = GetCacheEntry(propertyInfo, modelType); + + return cacheEntry.Metadata; + } + private static DefaultModelBindingMessageProvider GetMessageProvider(IOptions optionsAccessor) { if (optionsAccessor == null) @@ -151,10 +180,17 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata return cacheEntry; } - private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter) + private ModelMetadataCacheEntry GetCacheEntry(ParameterInfo parameter, Type modelType) { return _typeCache.GetOrAdd( - ModelMetadataIdentity.ForParameter(parameter), + ModelMetadataIdentity.ForParameter(parameter, modelType), + _cacheEntryFactory); + } + + private ModelMetadataCacheEntry GetCacheEntry(PropertyInfo property, Type modelType) + { + return _typeCache.GetOrAdd( + ModelMetadataIdentity.ForProperty(modelType, property.Name, property.DeclaringType), _cacheEntryFactory); } @@ -165,6 +201,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { details = CreateParameterDetails(key); } + else if (key.MetadataKind == ModelMetadataKind.Property) + { + details = CreateSinglePropertyDetails(key); + } else { details = CreateTypeDetails(key); @@ -174,6 +214,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata return new ModelMetadataCacheEntry(metadata, details); } + private DefaultMetadataDetails CreateSinglePropertyDetails(ModelMetadataIdentity propertyKey) + { + var propertyHelpers = PropertyHelper.GetVisibleProperties(propertyKey.ContainerType); + for (var i = 0; i < propertyHelpers.Length; i++) + { + var propertyHelper = propertyHelpers[i]; + if (propertyHelper.Name == propertyKey.Name) + { + return CreateSinglePropertyDetails(propertyKey, propertyHelper); + } + } + + Debug.Fail($"Unable to find property '{propertyKey.Name}' on type '{propertyKey.ContainerType}."); + return null; + } + private ModelMetadataCacheEntry GetMetadataCacheEntryForObjectType() { var key = ModelMetadataIdentity.ForType(typeof(object)); @@ -217,35 +273,48 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata for (var i = 0; i < propertyHelpers.Length; i++) { var propertyHelper = propertyHelpers[i]; + var propertyKey = ModelMetadataIdentity.ForProperty( propertyHelper.Property.PropertyType, propertyHelper.Name, key.ModelType); - var attributes = ModelAttributes.GetAttributesForProperty( - key.ModelType, - propertyHelper.Property); - - var propertyEntry = new DefaultMetadataDetails(propertyKey, attributes); - if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPublic == true) - { - var getter = PropertyHelper.MakeNullSafeFastPropertyGetter(propertyHelper.Property); - propertyEntry.PropertyGetter = getter; - } - - if (propertyHelper.Property.CanWrite && - propertyHelper.Property.SetMethod?.IsPublic == true && - !key.ModelType.GetTypeInfo().IsValueType) - { - propertyEntry.PropertySetter = propertyHelper.ValueSetter; - } - + var propertyEntry = CreateSinglePropertyDetails(propertyKey, propertyHelper); propertyEntries.Add(propertyEntry); } return propertyEntries.ToArray(); } + private DefaultMetadataDetails CreateSinglePropertyDetails( + ModelMetadataIdentity propertyKey, + PropertyHelper propertyHelper) + { + Debug.Assert(propertyKey.MetadataKind == ModelMetadataKind.Property); + var containerType = propertyKey.ContainerType; + + var attributes = ModelAttributes.GetAttributesForProperty( + containerType, + propertyHelper.Property, + propertyKey.ModelType); + + var propertyEntry = new DefaultMetadataDetails(propertyKey, attributes); + if (propertyHelper.Property.CanRead && propertyHelper.Property.GetMethod?.IsPublic == true) + { + var getter = PropertyHelper.MakeNullSafeFastPropertyGetter(propertyHelper.Property); + propertyEntry.PropertyGetter = getter; + } + + if (propertyHelper.Property.CanWrite && + propertyHelper.Property.SetMethod?.IsPublic == true && + !containerType.IsValueType) + { + propertyEntry.PropertySetter = propertyHelper.ValueSetter; + } + + return propertyEntry; + } + /// /// Creates the entry for a model . /// @@ -269,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { return new DefaultMetadataDetails( key, - ModelAttributes.GetAttributesForParameter(key.ParameterInfo)); + ModelAttributes.GetAttributesForParameter(key.ParameterInfo, key.ModelType)); } private class TypeCache : ConcurrentDictionary diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs index e5b59933ae..0f9a5bf103 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs @@ -135,9 +135,25 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public static ModelAttributes GetAttributesForProperty(Type type, PropertyInfo property) { - if (type == null) + return GetAttributesForProperty(type, property, property.PropertyType); + } + + /// + /// Gets the attributes for the given with the specified . + /// + /// The in which caller found . + /// + /// A for which attributes need to be resolved. + /// + /// The model type + /// + /// A instance with the attributes of the property and its . + /// + public static ModelAttributes GetAttributesForProperty(Type containerType, PropertyInfo property, Type modelType) + { + if (containerType == null) { - throw new ArgumentNullException(nameof(type)); + throw new ArgumentNullException(nameof(containerType)); } if (property == null) @@ -146,9 +162,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } var propertyAttributes = property.GetCustomAttributes(); - var typeAttributes = property.PropertyType.GetTypeInfo().GetCustomAttributes(); + var typeAttributes = modelType.GetCustomAttributes(); - var metadataType = GetMetadataType(type); + var metadataType = GetMetadataType(containerType); if (metadataType != null) { var metadataProperty = metadataType.GetRuntimeProperty(property.Name); @@ -174,12 +190,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding throw new ArgumentNullException(nameof(type)); } - var attributes = type.GetTypeInfo().GetCustomAttributes(); + var attributes = type.GetCustomAttributes(); var metadataType = GetMetadataType(type); if (metadataType != null) { - attributes = attributes.Concat(metadataType.GetTypeInfo().GetCustomAttributes()); + attributes = attributes.Concat(metadataType.GetCustomAttributes()); } return new ModelAttributes(attributes, propertyAttributes: null, parameterAttributes: null); @@ -205,9 +221,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes); } + /// + /// Gets the attributes for the given with the specified . + /// + /// + /// The for which attributes need to be resolved. + /// + /// The model type. + /// + /// A instance with the attributes of the parameter and its . + /// + public static ModelAttributes GetAttributesForParameter(ParameterInfo parameterInfo, Type modelType) + { + if (parameterInfo == null) + { + throw new ArgumentNullException(nameof(parameterInfo)); + } + + if (modelType == null) + { + throw new ArgumentNullException(nameof(modelType)); + } + + // Prior versions called IModelMetadataProvider.GetMetadataForType(...) and therefore + // GetAttributesForType(...) for parameters. Maintain that set of attributes (including those from an + // ModelMetadataTypeAttribute reference) for back-compatibility. + var typeAttributes = GetAttributesForType(modelType).TypeAttributes; + var parameterAttributes = parameterInfo.GetCustomAttributes(); + + return new ModelAttributes(typeAttributes, propertyAttributes: null, parameterAttributes); + } + private static Type GetMetadataType(Type type) { - return type.GetTypeInfo().GetCustomAttribute()?.MetadataType; + return type.GetCustomAttribute()?.MetadataType; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs index 973058609b..51ed9c3034 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Logging; @@ -283,6 +284,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding ModelBindingContext modelBindingContext, ModelBindingResult modelBindingResult) { + RecalculateModelMetadata(parameter, modelBindingResult, ref metadata); + if (!modelBindingResult.IsModelSet && metadata.IsBindingRequired) { // Enforce BindingBehavior.Required (e.g., [BindRequired]) @@ -330,5 +333,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadata); } } + + private void RecalculateModelMetadata( + ParameterDescriptor parameter, + ModelBindingResult modelBindingResult, + ref ModelMetadata metadata) + { + // Attempt to recalculate ModelMetadata for top level parameters and properties using the actual + // model type. This ensures validation uses a combination of top-level validation metadata + // as well as metadata on the actual, rather than declared, model type. + + if (!modelBindingResult.IsModelSet || + modelBindingResult.Model == null || + !(_modelMetadataProvider is ModelMetadataProvider modelMetadataProvider)) + { + return; + } + + var modelType = modelBindingResult.Model.GetType(); + if (parameter is IParameterInfoParameterDescriptor parameterInfoParameter) + { + var parameterInfo = parameterInfoParameter.ParameterInfo; + if (modelType != parameterInfo.ParameterType) + { + metadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo, modelType); + } + } + else if (parameter is IPropertyInfoParameterDescriptor propertyInfoParameter) + { + var propertyInfo = propertyInfoParameter.PropertyInfo; + if (modelType != propertyInfo.PropertyType) + { + metadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, modelType); + } + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs index dcf212e077..099b86192c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs @@ -3,11 +3,15 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { - public class HandlerParameterDescriptor : ParameterDescriptor + public class HandlerParameterDescriptor : ParameterDescriptor, IParameterInfoParameterDescriptor { + /// + /// Gets or sets the . + /// public ParameterInfo ParameterInfo { get; set; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs index 56cbdc40f7..cb02222300 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs @@ -3,11 +3,17 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { - public class PageBoundPropertyDescriptor : ParameterDescriptor + public class PageBoundPropertyDescriptor : ParameterDescriptor, IPropertyInfoParameterDescriptor { + /// + /// Gets or sets the for this property. + /// public PropertyInfo Property { get; set; } + + PropertyInfo IPropertyInfoParameterDescriptor.PropertyInfo => Property; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs index 4286f263c9..8994835817 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using System.Reflection; using Microsoft.Extensions.Options; using Xunit; @@ -263,6 +264,173 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata Assert.Same(metadata1, metadata2); } + [Fact] + public void GetMetadataForParameter_WithModelType_ReturnsCombinedModelMetadata() + { + // Arrange + var parameter = GetType() + .GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance) + .GetParameters()[0]; + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType)); + + // Assert + Assert.Equal(ModelMetadataKind.Parameter, metadata.MetadataKind); + Assert.Equal(typeof(DerivedModelType), metadata.ModelType); + + var defaultModelMetadata = Assert.IsType(metadata); + + Assert.Collection( + defaultModelMetadata.Attributes.Attributes, + a => Assert.Equal("OnParameter", Assert.IsType(a).Value), + a => Assert.Equal("OnDerivedType", Assert.IsType(a).Value), + a => Assert.Equal("OnType", Assert.IsType(a).Value)); + + Assert.Collection( + metadata.Properties.OrderBy(p => p.Name), + p => + { + Assert.Equal(nameof(DerivedModelType.DerivedProperty), p.Name); + + var defaultPropertyMetadata = Assert.IsType(p); + Assert.Collection( + defaultPropertyMetadata.Attributes.Attributes.OfType(), + a => Assert.Equal("OnDerivedProperty", Assert.IsType(a).Value)); + }, + p => + { + Assert.Equal(nameof(DerivedModelType.Property1), p.Name); + + var defaultPropertyMetadata = Assert.IsType(p); + Assert.Collection( + defaultPropertyMetadata.Attributes.Attributes.OfType(), + a => Assert.Equal("OnProperty", Assert.IsType(a).Value), + a => Assert.Equal("OnPropertyType", Assert.IsType(a).Value)); + }, + p => + { + Assert.Equal(nameof(DerivedModelType.Property2), p.Name); + }); + } + + [Fact] + public void GetMetadataForParameter_WithModelType_CachesResults() + { + // Arrange + var parameter = GetType() + .GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance) + .GetParameters()[0]; + var provider = CreateProvider(); + + // Act + var metadata1 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType)); + var metadata2 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType)); + + // Assert + Assert.Same(metadata1, metadata2); + } + + [Fact] + public void GetMetadataForParameter_WithModelType_VariesByModelType() + { + // Arrange + var parameter = GetType() + .GetMethod(nameof(GetMetadataForParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Instance) + .GetParameters()[0]; + var provider = CreateProvider(); + + // Act + var metadata1 = provider.GetMetadataForParameter(parameter, typeof(DerivedModelType)); + var metadata2 = provider.GetMetadataForParameter(parameter, typeof(object)); + + // Assert + Assert.NotSame(metadata1, metadata2); + } + + [Fact] + public void GetMetadataForProperty_WithModelType_ReturnsCombinedModelMetadata() + { + // Arrange + var property = typeof(TestContainer) + .GetProperty(nameof(TestContainer.ModelProperty)); + var provider = CreateProvider(); + + // Act + var metadata = provider.GetMetadataForProperty(property, typeof(DerivedModelType)); + + // Assert + Assert.Equal(ModelMetadataKind.Property, metadata.MetadataKind); + Assert.Equal(typeof(DerivedModelType), metadata.ModelType); + + var defaultModelMetadata = Assert.IsType(metadata); + + Assert.Collection( + defaultModelMetadata.Attributes.Attributes, + a => Assert.Equal("OnProperty", Assert.IsType(a).Value), + a => Assert.Equal("OnDerivedType", Assert.IsType(a).Value), + a => Assert.Equal("OnType", Assert.IsType(a).Value)); + + Assert.Collection( + metadata.Properties.OrderBy(p => p.Name), + p => + { + Assert.Equal(nameof(DerivedModelType.DerivedProperty), p.Name); + + var defaultPropertyMetadata = Assert.IsType(p); + Assert.Collection( + defaultPropertyMetadata.Attributes.Attributes.OfType(), + a => Assert.Equal("OnDerivedProperty", Assert.IsType(a).Value)); + }, + p => + { + Assert.Equal(nameof(DerivedModelType.Property1), p.Name); + + var defaultPropertyMetadata = Assert.IsType(p); + Assert.Collection( + defaultPropertyMetadata.Attributes.Attributes.OfType(), + a => Assert.Equal("OnProperty", Assert.IsType(a).Value), + a => Assert.Equal("OnPropertyType", Assert.IsType(a).Value)); + }, + p => + { + Assert.Equal(nameof(DerivedModelType.Property2), p.Name); + }); + } + + [Fact] + public void GetMetadataForProperty_WithModelType_CachesResults() + { + // Arrange + var property = typeof(TestContainer) + .GetProperty(nameof(TestContainer.ModelProperty)); + var provider = CreateProvider(); + + // Act + var metadata1 = provider.GetMetadataForProperty(property, typeof(DerivedModelType)); + var metadata2 = provider.GetMetadataForProperty(property, typeof(DerivedModelType)); + + // Assert + Assert.Same(metadata1, metadata2); + } + + [Fact] + public void GetMetadataForProperty_WithModelType_VariesByModelType() + { + // Arrange + var property = typeof(TestContainer) + .GetProperty(nameof(TestContainer.ModelProperty)); + var provider = CreateProvider(); + + // Act + var metadata1 = provider.GetMetadataForProperty(property, typeof(DerivedModelType)); + var metadata2 = provider.GetMetadataForProperty(property, typeof(object)); + + // Assert + Assert.NotSame(metadata1, metadata2); + } + private static DefaultModelMetadataProvider CreateProvider() { return new DefaultModelMetadataProvider( @@ -321,5 +489,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { public new string Property { get; set; } } + + [Model("OnDerivedType")] + private class DerivedModelType : ModelType + { + [Model("OnDerivedProperty")] + public string DerivedProperty { get; set; } + } + + private class TestContainer + { + [Model("OnProperty")] + public ModelType ModelProperty { get; set; } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs index 3b0b3b0d92..3174e0110d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs @@ -5,7 +5,6 @@ using System; using System.ComponentModel.DataAnnotations; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using Xunit; namespace Microsoft.AspNetCore.Mvc.ModelBinding @@ -242,6 +241,55 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Assert.IsType(Assert.Single(attributes.TypeAttributes)); } + [Fact] + public void GetAttributesForParameter_WithModelType_IncludesTypeAttributes() + { + // Arrange + var parameters = typeof(MethodWithParamAttributesType) + .GetMethod(nameof(MethodWithParamAttributesType.Method)) + .GetParameters(); + + // Act + var attributes = ModelAttributes.GetAttributesForParameter(parameters[2], typeof(DerivedModelWithAttributes)); + + // Assert + Assert.Collection( + attributes.Attributes, + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute)); + Assert.IsType(Assert.Single(attributes.ParameterAttributes)); + Assert.Null(attributes.PropertyAttributes); + Assert.Collection( + attributes.TypeAttributes, + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute)); + } + + [Fact] + public void GetAttributesForProperty_WithModelType_IncludesTypeAttributes() + { + // Arrange + var property = typeof(MergedAttributes) + .GetProperty(nameof(MergedAttributes.BaseModel)); + + // Act + var attributes = ModelAttributes.GetAttributesForProperty(typeof(MergedAttributes), property, typeof(DerivedModelWithAttributes)); + + // Assert + Assert.Collection( + attributes.Attributes, + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute)); + Assert.IsType(Assert.Single(attributes.PropertyAttributes)); + Assert.Null(attributes.ParameterAttributes); + Assert.Collection( + attributes.TypeAttributes, + attribute => Assert.IsType(attribute), + attribute => Assert.IsType(attribute)); + } + [ClassValidator] private class BaseModel { @@ -272,6 +320,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } + [ModelBinder(Name = "Custom")] + private class DerivedModelWithAttributes : BaseModel + { + } + [ModelMetadataType(typeof(BaseModel))] private class BaseViewModel { @@ -313,6 +366,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { [Required] public PropertyType Property { get; set; } + + [BindRequired] + public BaseModel BaseModel { get; set; } } private class MergedAttributesMetadata diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index 6a49060f19..7a605c4640 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -491,6 +492,220 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding actionContext.ModelState.Single().Value.Errors.Single().ErrorMessage); } + [Fact] + public async Task BindModelAsync_ForParameter_UsesValidationFromActualModel_WhenDerivedModelIsSet() + { + // Arrange + var method = GetType().GetMethod(nameof(TestMethodWithoutAttributes), BindingFlags.NonPublic | BindingFlags.Instance); + var parameter = method.GetParameters()[0]; + var parameterDescriptor = new ControllerParameterDescriptor + { + ParameterInfo = parameter, + Name = parameter.Name, + }; + + var actionContext = GetControllerContext(); + var modelMetadataProvider = new TestModelMetadataProvider(); + + var model = new DerivedPerson(); + var modelBindingResult = ModelBindingResult.Success(model); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameter); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + CreateMockValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.Same(model, result.Model); + + Assert.False(actionContext.ModelState.IsValid); + Assert.Collection( + actionContext.ModelState, + kvp => + { + Assert.Equal($"{parameter.Name}.{nameof(DerivedPerson.DerivedProperty)}", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("The DerivedProperty field is required.", error.ErrorMessage); + }); + } + + [Fact] + public async Task BindModelAsync_ForParameter_UsesValidationFromParameter_WhenDerivedModelIsSet() + { + // Arrange + var method = GetType().GetMethod(nameof(TestMethodWithAttributes), BindingFlags.NonPublic | BindingFlags.Instance); + var parameter = method.GetParameters()[0]; + var parameterDescriptor = new ControllerParameterDescriptor + { + ParameterInfo = parameter, + Name = parameter.Name, + }; + + var actionContext = GetControllerContext(); + var modelMetadataProvider = new TestModelMetadataProvider(); + + var model = new DerivedPerson { DerivedProperty = "SomeValue" }; + var modelBindingResult = ModelBindingResult.Success(model); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameter); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + CreateMockValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.Same(model, result.Model); + + Assert.False(actionContext.ModelState.IsValid); + Assert.Collection( + actionContext.ModelState, + kvp => + { + Assert.Equal(parameter.Name, kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("Always Invalid", error.ErrorMessage); + }); + } + + [Fact] + public async Task BindModelAsync_ForProperty_UsesValidationFromActualModel_WhenDerivedModelIsSet() + { + // Arrange + var property = typeof(TestController).GetProperty(nameof(TestController.Model)); + var parameterDescriptor = new ControllerBoundPropertyDescriptor + { + PropertyInfo = property, + Name = property.Name, + }; + + var actionContext = GetControllerContext(); + var modelMetadataProvider = new TestModelMetadataProvider(); + + var model = new DerivedModel(); + var modelBindingResult = ModelBindingResult.Success(model); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + var modelMetadata = modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + CreateMockValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.Same(model, result.Model); + + Assert.False(actionContext.ModelState.IsValid); + Assert.Collection( + actionContext.ModelState, + kvp => + { + Assert.Equal($"{property.Name}.{nameof(DerivedPerson.DerivedProperty)}", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("The DerivedProperty field is required.", error.ErrorMessage); + }); + } + + [Fact] + public async Task BindModelAsync_ForProperty_UsesValidationOnProperty_WhenDerivedModelIsSet() + { + // Arrange + var property = typeof(TestControllerWithValidatedProperties).GetProperty(nameof(TestControllerWithValidatedProperties.Model)); + var parameterDescriptor = new ControllerBoundPropertyDescriptor + { + PropertyInfo = property, + Name = property.Name, + }; + + var actionContext = GetControllerContext(); + var modelMetadataProvider = new TestModelMetadataProvider(); + + var model = new DerivedModel { DerivedProperty = "some value" }; + var modelBindingResult = ModelBindingResult.Success(model); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + var modelMetadata = modelMetadataProvider.GetMetadataForProperty(property.DeclaringType, property.Name); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + CreateMockValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.Same(model, result.Model); + + Assert.False(actionContext.ModelState.IsValid); + Assert.Collection( + actionContext.ModelState, + kvp => + { + Assert.Equal($"{property.Name}", kvp.Key); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("Always Invalid", error.ErrorMessage); + }); + } + private static ControllerContext GetControllerContext() { var services = new ServiceCollection(); @@ -641,6 +856,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public IList Kids { get; } = new List(); } + private class DerivedPerson : Person + { + [Required] + public string DerivedProperty { get; set; } + } + + [Required] + private Person PersonProperty { get; set; } + public abstract class FakeModelMetadata : ModelMetadata { public FakeModelMetadata() @@ -648,5 +872,44 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { } } + + private void TestMethodWithoutAttributes(Person person) { } + + private void TestMethodWithAttributes([Required][AlwaysInvalid] Person person) { } + + private class TestController + { + public BaseModel Model { get; set; } + } + + private class TestControllerWithValidatedProperties + { + [AlwaysInvalid] + [Required] + public BaseModel Model { get; set; } + } + + private class BaseModel + { + } + + private class DerivedModel + { + [Required] + public string DerivedProperty { get; set; } + } + + private class AlwaysInvalidAttribute : ValidationAttribute + { + public AlwaysInvalidAttribute() + { + ErrorMessage = "Always Invalid"; + } + + public override bool IsValid(object value) + { + return false; + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs index c93092b339..6761579f04 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs @@ -3,15 +3,14 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Reflection; -using System.Text; using System.Threading.Tasks; using BasicWebSite.Models; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs index 590e1d02a9..fe8d4f300b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs @@ -7,7 +7,6 @@ using System.Net.Http; using System.Threading.Tasks; using AngleSharp.Dom.Html; using AngleSharp.Parser.Html; -using Xunit; using Xunit.Sdk; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -32,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public static async Task AssertStatusCodeAsync(this HttpResponseMessage response, HttpStatusCode expectedStatusCode) { - if (response.StatusCode == HttpStatusCode.OK) + if (response.StatusCode == expectedStatusCode) { return response; } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs index 2907b248c8..2425c21f3a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs @@ -3,11 +3,12 @@ using System.Net; using System.Net.Http; -using System.Net.Http.Headers; using System.Text; using System.Threading.Tasks; +using FormatterWebSite.Models; using Microsoft.AspNetCore.Testing.xunit; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -217,5 +218,100 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode); } + + [Fact] + public async Task BindingWorksForPolymorphicTypes() + { + // Act + var response = await Client.GetAsync("PolymorphicBinding/ModelBound?DerivedProperty=Test"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.Equal("Test", result.DerivedProperty); + } + + [Fact] + public async Task ValidationUsesModelMetadataFromActualModelType_ForModelBoundParameters() + { + // Act + var response = await Client.GetAsync("PolymorphicBinding/ModelBound"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var result = JObject.Parse(await response.Content.ReadAsStringAsync()); + Assert.Collection( + result.Properties(), + p => + { + Assert.Equal("DerivedProperty", p.Name); + var value = Assert.IsType(p.Value); + Assert.Equal("The DerivedProperty field is required.", value.First); + }); + } + + [Fact] + public async Task InputFormatterWorksForPolymorphicTypes() + { + // Act + var input = "Test"; + var response = await Client.PostAsJsonAsync("PolymorphicBinding/InputFormatted", input); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.Equal(input, result.DerivedProperty); + } + + [Fact] + public async Task ValidationUsesModelMetadataFromActualModelType_ForInputFormattedParameters() + { + // Act + var response = await Client.PostAsJsonAsync("PolymorphicBinding/InputFormatted", string.Empty); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var result = JObject.Parse(await response.Content.ReadAsStringAsync()); + Assert.Collection( + result.Properties(), + p => + { + Assert.Equal("DerivedProperty", p.Name); + var value = Assert.IsType(p.Value); + Assert.Equal("The DerivedProperty field is required.", value.First); + }); + } + + [Fact] + public async Task InputFormatterWorksForPolymorphicProperties() + { + // Act + var input = "Test"; + var response = await Client.PostAsJsonAsync("PolymorhpicPropertyBinding/Action", input); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var result = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.Equal(input, result.DerivedProperty); + } + + [Fact] + public async Task ValidationUsesModelMetadataFromActualModelType_ForInputFormattedProperties() + { + // Act + var response = await Client.PostAsJsonAsync("PolymorhpicPropertyBinding/Action", string.Empty); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var result = JObject.Parse(await response.Content.ReadAsStringAsync()); + Assert.Collection( + result.Properties(), + p => + { + Assert.Equal("DerivedProperty", p.Name); + var value = Assert.IsType(p.Value); + Assert.Equal("The DerivedProperty field is required.", value.First); + }); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 524f8bd977..d97e128727 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Testing; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -738,6 +739,65 @@ Hello from /Pages/WithViewStart/Index.cshtml!"; } } + [Fact] + public async Task PolymorphicPropertiesOnPageModelsAreBound() + { + // Arrange + var name = "TestName"; + var age = 23; + var expected = $"Name = {name}, Age = {age}"; + var request = new HttpRequestMessage(HttpMethod.Post, "Pages/PropertyBinding/PolymorphicBinding") + { + Content = new FormUrlEncodedContent(new Dictionary + { + { "Name", name }, + { "Age", age.ToString() }, + }), + }; + await AddAntiforgeryHeaders(request); + + // Act + var response = await Client.SendAsync(request); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal(expected, content); + } + + [Fact] + public async Task PolymorphicPropertiesOnPageModelsAreValidated() + { + // Arrange + var name = "TestName"; + var age = 123; + var expected = $"Name = {name}, Age = {age}"; + var request = new HttpRequestMessage(HttpMethod.Post, "Pages/PropertyBinding/PolymorphicBinding") + { + Content = new FormUrlEncodedContent(new Dictionary + { + { "Name", name }, + { "Age", age.ToString() }, + }), + }; + await AddAntiforgeryHeaders(request); + + // Act + var response = await Client.SendAsync(request); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var result = JObject.Parse(await response.Content.ReadAsStringAsync()); + Assert.Collection( + result.Properties(), + p => + { + Assert.Equal("Age", p.Name); + var value = Assert.IsType(p.Value); + Assert.Equal("The field Age must be between 0 and 99.", value.First.ToString()); + }); + } + [Fact] public async Task HandlerMethodArgumentsAndPropertiesAreModelBound() { diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 9d4db8b9da..9e20cb447c 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; -using System.Security.Claims; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -36,7 +35,7 @@ namespace BasicWebSite var previous = options.InvalidModelStateResponseFactory; options.InvalidModelStateResponseFactory = context => { - var result = (BadRequestObjectResult) previous(context); + var result = (BadRequestObjectResult)previous(context); if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute)) { result.ContentTypes.Clear(); diff --git a/test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs b/test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs new file mode 100644 index 0000000000..27a2643cfc --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs @@ -0,0 +1,25 @@ +// 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 FormatterWebSite.Models; +using Microsoft.AspNetCore.Mvc; + +namespace FormatterWebSite.Controllers +{ + public class PolymorhpicPropertyBindingController : ControllerBase + { + [FromBody] + public IModel Person { get; set; } + + [HttpPost] + public IActionResult Action() + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(Person); + } + } +} diff --git a/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs b/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs new file mode 100644 index 0000000000..9358ec50d6 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs @@ -0,0 +1,32 @@ +// 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 FormatterWebSite.Models; +using Microsoft.AspNetCore.Mvc; + +namespace FormatterWebSite.Controllers +{ + public class PolymorphicBindingController : ControllerBase + { + public IActionResult ModelBound([ModelBinder(typeof(PolymorphicBinder))] BaseModel person) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(person); + } + + [HttpPost] + public IActionResult InputFormatted([FromBody] IModel person) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(person); + } + } +} diff --git a/test/WebSites/FormatterWebSite/IModelConverter.cs b/test/WebSites/FormatterWebSite/IModelConverter.cs new file mode 100644 index 0000000000..1ce4131f17 --- /dev/null +++ b/test/WebSites/FormatterWebSite/IModelConverter.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using FormatterWebSite.Models; +using Newtonsoft.Json; + +namespace FormatterWebSite +{ + public class IModelConverter : JsonConverter + { + public override bool CanConvert(Type objectType) + { + return objectType == typeof(IModel); + } + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return new DerivedModel + { + DerivedProperty = reader.Value.ToString(), + }; + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/BaseModel.cs b/test/WebSites/FormatterWebSite/Models/BaseModel.cs new file mode 100644 index 0000000000..b7b6567b1e --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/BaseModel.cs @@ -0,0 +1,10 @@ +// 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 FormatterWebSite.Models +{ + public class BaseModel + { + public string BaseProperty { get; set; } + } +} diff --git a/test/WebSites/FormatterWebSite/Models/DerivedModel.cs b/test/WebSites/FormatterWebSite/Models/DerivedModel.cs new file mode 100644 index 0000000000..2b10ac357a --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/DerivedModel.cs @@ -0,0 +1,14 @@ +// 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.ComponentModel.DataAnnotations; + +namespace FormatterWebSite.Models +{ + public class DerivedModel : BaseModel, IModel + { + [Required] + [StringLength(10)] + public string DerivedProperty { get; set; } + } +} diff --git a/test/WebSites/FormatterWebSite/Models/IModel.cs b/test/WebSites/FormatterWebSite/Models/IModel.cs new file mode 100644 index 0000000000..ce6e4b6d9b --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/IModel.cs @@ -0,0 +1,9 @@ +// 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 FormatterWebSite.Models +{ + public interface IModel + { + } +} diff --git a/test/WebSites/FormatterWebSite/PolymorphicBinder.cs b/test/WebSites/FormatterWebSite/PolymorphicBinder.cs new file mode 100644 index 0000000000..e7eefb71c2 --- /dev/null +++ b/test/WebSites/FormatterWebSite/PolymorphicBinder.cs @@ -0,0 +1,24 @@ +// 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.Threading.Tasks; +using FormatterWebSite.Models; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace FormatterWebSite.Controllers +{ + public class PolymorphicBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var model = new DerivedModel + { + DerivedProperty = bindingContext.ValueProvider.GetValue(nameof(DerivedModel.DerivedProperty)).FirstValue, + }; + + bindingContext.Result = ModelBindingResult.Success(model); + + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/test/WebSites/FormatterWebSite/Startup.cs index dd3beb9121..ee2744a028 100644 --- a/test/WebSites/FormatterWebSite/Startup.cs +++ b/test/WebSites/FormatterWebSite/Startup.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; @@ -19,6 +20,8 @@ namespace FormatterWebSite options.InputFormatters.Add(new StringInputFormatter()); }) .AddXmlDataContractSerializerFormatters(); + + services.Configure(options => { options.SerializerSettings.Converters.Insert(0, new IModelConverter()); }); } diff --git a/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs b/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs new file mode 100644 index 0000000000..19cc1d40a9 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs @@ -0,0 +1,9 @@ +// 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 RazorPagesWebSite +{ + public interface IUserModel + { + } +} diff --git a/test/WebSites/RazorPagesWebSite/Models/UserModel.cs b/test/WebSites/RazorPagesWebSite/Models/UserModel.cs index 62c2bf8cae..80c2d8fdc8 100644 --- a/test/WebSites/RazorPagesWebSite/Models/UserModel.cs +++ b/test/WebSites/RazorPagesWebSite/Models/UserModel.cs @@ -5,12 +5,17 @@ using System.ComponentModel.DataAnnotations; namespace RazorPagesWebSite { - public class UserModel + public class UserModel : IUserModel { [Required] public string Name { get; set; } [Range(0, 99)] public int Age { get; set; } + + public override string ToString() + { + return $"Name = {Name}, Age = {Age}"; + } } } diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs new file mode 100644 index 0000000000..0986986da0 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs @@ -0,0 +1,24 @@ +// 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; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite +{ + public class PolymorphicBinding : PageModel + { + [ModelBinder(typeof(PolymorphicModelBinder))] + public IUserModel UserModel { get; set; } + + public IActionResult OnPost() + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return new ContentResult { Content = UserModel.ToString() }; + } + } +} diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml new file mode 100644 index 0000000000..7bde2525de --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml @@ -0,0 +1,6 @@ +@page +@model PolymorphicBinding + +
+ @Html.AntiForgeryToken() +
\ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs b/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs new file mode 100644 index 0000000000..bce3d79b8a --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs @@ -0,0 +1,32 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace RazorPagesWebSite +{ + public class PolymorphicModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + var ageValue = bindingContext.ValueProvider.GetValue(nameof(UserModel.Age)); + var age = 0; + if (ageValue.Length != 0) + { + age = int.Parse(ageValue.FirstValue); + } + + + var model = new UserModel + { + Name = bindingContext.ValueProvider.GetValue(nameof(UserModel.Name)).FirstValue, + Age = age, + }; + + bindingContext.Result = ModelBindingResult.Success(model); + + return Task.CompletedTask; + } + } +} From d2bb674b0a665253d64696cdce26284b4bd930eb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 6 Jul 2018 16:44:07 -0700 Subject: [PATCH 087/316] Add support for default response (#8028) * Add support for default response Fixes #6828 --- .../ApiResponseTypeProvider.cs | 75 +++++++------ .../ApiConventionTypeAttribute.cs | 3 +- .../IApiDefaultResponseMetadataProvider.cs | 12 +++ .../DefaultApiConventions.cs | 4 + .../ProducesDefaultResponseTypeAttribute.cs | 49 +++++++++ .../ApiResponseTypeProviderTest.cs | 102 ++++++++++++++++++ .../ApiConventionTypeAttributeTest.cs | 35 +++--- .../ApiExplorer/ApiConventionResultTest.cs | 4 + .../ApiExplorerTest.cs | 18 +++- 9 files changed, 253 insertions(+), 49 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index d6c7cd7661..ec791aaee0 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer _mvcOptions = mvcOptions; } - public IList GetApiResponseTypes(ControllerActionDescriptor action) + public ICollection GetApiResponseTypes(ControllerActionDescriptor action) { // We only provide response info if we can figure out a type that is a user-data type. // Void /Task object/IActionResult will result in no data. @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); var responseMetadataAttributes = GetResponseMetadataAttributes(action); - if (responseMetadataAttributes.Count == 0 && + if (responseMetadataAttributes.Count == 0 && action.Properties.TryGetValue(typeof(ApiConventionResult), out var result)) { // Action does not have any conventions. Use conventions on it if present. @@ -67,14 +67,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer .ToArray(); } - private IList GetApiResponseTypes( + private ICollection GetApiResponseTypes( IReadOnlyList responseMetadataAttributes, Type type) { - var results = new List(); - - // Build list of all possible return types (and status codes) for an action. - var objectTypes = new Dictionary(); + var results = new Dictionary(); // Get the content type that the action explicitly set to support. // Walk through all 'filter' attributes in order, and allow each one to see or override @@ -86,7 +83,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { metadataAttribute.SetContentTypes(contentTypes); - if (metadataAttribute.Type == typeof(void) && + ApiResponseType apiResponseType; + + if (metadataAttribute is IApiDefaultResponseMetadataProvider) + { + apiResponseType = new ApiResponseType + { + IsDefaultResponse = true, + Type = metadataAttribute.Type, + }; + } + else if (metadataAttribute.Type == typeof(void) && type != null && (metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created)) { @@ -94,20 +101,38 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer // In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a // [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred // from the return type. - objectTypes[metadataAttribute.StatusCode] = type; + apiResponseType = new ApiResponseType + { + StatusCode = metadataAttribute.StatusCode, + Type = type, + }; } else if (metadataAttribute.Type != null) { - objectTypes[metadataAttribute.StatusCode] = metadataAttribute.Type; + apiResponseType = new ApiResponseType + { + StatusCode = metadataAttribute.StatusCode, + Type = metadataAttribute.Type, + }; } + else + { + continue; + } + + results[apiResponseType.StatusCode] = apiResponseType; } } // Set the default status only when no status has already been set explicitly - if (objectTypes.Count == 0 && type != null) + if (results.Count == 0 && type != null) { - objectTypes[StatusCodes.Status200OK] = type; + results[StatusCodes.Status200OK] = new ApiResponseType + { + StatusCode = StatusCodes.Status200OK, + Type = type, + }; } if (contentTypes.Count == 0) @@ -117,25 +142,15 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); - foreach (var objectType in objectTypes) + foreach (var apiResponse in results.Values) { - if (objectType.Value == null || objectType.Value == typeof(void)) + var responseType = apiResponse.Type; + if (responseType == null || responseType == typeof(void)) { - results.Add(new ApiResponseType() - { - StatusCode = objectType.Key, - Type = objectType.Value - }); - continue; } - var apiResponseType = new ApiResponseType() - { - Type = objectType.Value, - StatusCode = objectType.Key, - ModelMetadata = _modelMetadataProvider.GetMetadataForType(objectType.Value) - }; + apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType); foreach (var contentType in contentTypes) { @@ -143,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( contentType, - objectType.Value); + responseType); if (formatterSupportedContentTypes == null) { @@ -152,7 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { - apiResponseType.ApiResponseFormats.Add(new ApiResponseFormat() + apiResponse.ApiResponseFormats.Add(new ApiResponseFormat { Formatter = (IOutputFormatter)responseTypeMetadataProvider, MediaType = formatterSupportedContentType, @@ -160,11 +175,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } } } - - results.Add(apiResponseType); } - return results; + return results.Values; } private Type GetDeclaredReturnType(ControllerActionDescriptor action) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs index 9c44452961..e8c10cd3ac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc var errorMessage = Resources.FormatApiConvention_UnsupportedAttributesOnConvention( methodDisplayName, Environment.NewLine + string.Join(Environment.NewLine, unsupportedAttributes) + Environment.NewLine, - $"{nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"); + $"{nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"); throw new ArgumentException(errorMessage, nameof(conventionType)); } @@ -83,6 +83,7 @@ namespace Microsoft.AspNetCore.Mvc private static bool IsAllowedAttribute(object attribute) { return attribute is ProducesResponseTypeAttribute || + attribute is ProducesDefaultResponseTypeAttribute || attribute is ApiConventionNameMatchAttribute; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs new file mode 100644 index 0000000000..a841fc9f0a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs @@ -0,0 +1,12 @@ +// 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.ApiExplorer +{ + /// + /// Provides a return type for all HTTP status codes that are not covered by other instances. + /// + public interface IApiDefaultResponseMetadataProvider : IApiResponseMetadataProvider + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs index 46b3ec6e80..3508fb98cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs @@ -10,6 +10,7 @@ namespace Microsoft.AspNetCore.Mvc { [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Get( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] @@ -18,6 +19,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Post( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] @@ -27,6 +29,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Put( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] @@ -40,6 +43,7 @@ namespace Microsoft.AspNetCore.Mvc [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] public static void Delete( [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs new file mode 100644 index 0000000000..3022327a5b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs @@ -0,0 +1,49 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A filter that specifies the for all HTTP status codes that are not covered by . + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ProducesDefaultResponseTypeAttribute : Attribute, IApiDefaultResponseMetadataProvider + { + /// + /// Initializes an instance of . + /// + public ProducesDefaultResponseTypeAttribute() + : this(typeof(void)) + { + } + + /// + /// Initializes an instance of . + /// + /// The of object that is going to be written in the response. + public ProducesDefaultResponseTypeAttribute(Type type) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + } + + /// + /// Gets or sets the type of the value returned by an action. + /// + public Type Type { get; } + + /// + /// Gets or sets the HTTP status code of the response. + /// + public int StatusCode { get; } + + /// + void IApiResponseMetadataProvider.SetContentTypes(MediaTypeCollection contentTypes) + { + // Users are supposed to use the 'Produces' attribute to set the content types that an action can support. + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 7f3247deac..29543d26f1 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -72,6 +72,60 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public Task> Get(int id) => null; } + [Fact] + public void GetApiResponseTypes_CombinesFilters() + { + // Arrange + var filterDescriptors = new[] + { + new FilterDescriptor(new ProducesResponseTypeAttribute(400), FilterScope.Global), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(object), 201), FilterScope.Controller), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(ProblemDetails), 400), FilterScope.Controller), + new FilterDescriptor(new ProducesResponseTypeAttribute(typeof(BaseModel), 201), FilterScope.Action), + new FilterDescriptor(new ProducesResponseTypeAttribute(404), FilterScope.Action), + }; + + var actionDescriptor = new ControllerActionDescriptor + { + FilterDescriptors = filterDescriptors, + MethodInfo = typeof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController).GetMethod(nameof(GetApiResponseTypes_ReturnsResponseTypesFromActionIfPresentController.Get)), + }; + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(typeof(ProblemDetails), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + [Fact] public void GetApiResponseTypes_ReturnsResponseTypesFromApiConventionItem() { @@ -159,6 +213,54 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public Task> PostModel(int id, BaseModel model) => null; } + [Fact] + public void GetApiResponseTypes_ReturnsDefaultProblemResponse() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(201), + new ProducesResponseTypeAttribute(404), + new ProducesDefaultResponseTypeAttribute(typeof(SerializableError)), + }); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.True(responseType.IsDefaultResponse); + Assert.Equal(typeof(SerializableError), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs index edc7ce7747..d6ff2a28f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Testing; @@ -15,11 +16,10 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute() { // Arrange - var expected = $"Method {typeof(ConventionWithProducesAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - typeof(ProducesAttribute).FullName + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + + var expected = GetErrorMessage(methodName, attribute); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -38,11 +38,9 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfConventionMethodHasRouteAttribute() { // Arrange - var expected = $"Method {typeof(ConventionWithRouteAttribute).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - typeof(HttpGetAttribute).FullName + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWithRouteAttribute).FullName + '.' + nameof(ConventionWithRouteAttribute.Get); + var attribute = typeof(HttpGetAttribute); + var expected = GetErrorMessage(methodName, attribute); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -61,11 +59,9 @@ namespace Microsoft.AspNetCore.Mvc public void Constructor_ThrowsIfMultipleUnsupportedAttributesArePresentOnConvention() { // Arrange - var expected = $"Method {typeof(ConventionWitUnsupportedAttributes).FullName + ".Get"} is decorated with the following attributes that are not allowed on an API convention method:" + - Environment.NewLine + - string.Join(Environment.NewLine, typeof(ProducesAttribute).FullName, typeof(ServiceFilterAttribute).FullName, typeof(AuthorizeAttribute).FullName) + - Environment.NewLine + - $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + var methodName = typeof(ConventionWitUnsupportedAttributes).FullName + '.' + nameof(ConventionWitUnsupportedAttributes.Get); + var attributes = new[] { typeof(ProducesAttribute), typeof(ServiceFilterAttribute), typeof(AuthorizeAttribute) }; + var expected = GetErrorMessage(methodName, attributes); // Act & Assert ExceptionAssert.ThrowsArgument( @@ -82,5 +78,14 @@ namespace Microsoft.AspNetCore.Mvc [Authorize] public static void Get() { } } + + private static string GetErrorMessage(string methodName, params Type[] attributes) + { + return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs index 69079bd8b0..e1290a0421 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs @@ -111,6 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(200, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); } @@ -130,6 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(201, r.StatusCode), r => Assert.Equal(400, r.StatusCode)); } @@ -152,6 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(204, r.StatusCode), r => Assert.Equal(400, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); @@ -175,6 +178,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.True(result); Assert.Collection( conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), r => Assert.Equal(200, r.StatusCode), r => Assert.Equal(400, r.StatusCode), r => Assert.Equal(404, r.StatusCode)); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index e575967651..9b556a42a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -711,7 +711,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); - Assert.Equal(2, description.SupportedResponseTypes.Count); Assert.Collection( description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), @@ -749,7 +748,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); - Assert.Equal(2, description.SupportedResponseTypes.Count); Assert.Collection( description.SupportedResponseTypes.OrderBy(responseType => responseType.StatusCode), @@ -1171,6 +1169,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(Product).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode); @@ -1255,6 +1257,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(201, responseType.StatusCode); @@ -1283,6 +1289,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(204, responseType.StatusCode); @@ -1316,6 +1326,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Collection( description.SupportedResponseTypes.OrderBy(r => r.StatusCode), responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => { Assert.Equal(typeof(void).FullName, responseType.ResponseType); Assert.Equal(200, responseType.StatusCode); From d2cfbd2671ed4913090f5f9d798d8631fcf81ede Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 8 Jul 2018 12:22:39 -0700 Subject: [PATCH 088/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 152 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 78 insertions(+), 78 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3744df253b..3f80a4948b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34576 - 2.2.0-preview1-17090 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 + 2.2.0-preview1-34640 + 2.2.0-preview1-17099 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34576 + 2.2.0-preview1-34640 1.7.0 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 2.2.0-preview1-26618-02 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 - 2.0.0 - 2.1.0 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 + 2.0.7 + 2.1.1 2.2.0-preview1-26618-02 - 2.2.0-preview1-34576 - 2.2.0-preview1-34576 + 2.2.0-preview1-34640 + 2.2.0-preview1-34640 15.6.1 4.7.49 2.0.3 @@ -106,7 +106,7 @@ 4.6.0-preview1-26617-01 0.8.0 2.3.1 - 2.4.0-beta.1.build3945 + 2.4.0-rc.1.build4038 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index a8109db529..27e2e80f9a 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17090 -commithash:b19e903e946579cd9482089bce7d917e8bacd765 +version:2.2.0-preview1-17099 +commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 From dee479fda712e35cf740239587e438a8143850d8 Mon Sep 17 00:00:00 2001 From: hishamco Date: Sun, 1 Jul 2018 19:52:08 +0300 Subject: [PATCH 089/316] Add partial helper to Razor Page \ PageModel Fixes #7885 --- .../PageBase.cs | 27 +++++++++++ .../PageModel.cs | 27 +++++++++++ .../AntiforgeryTestHelper.cs | 24 +--------- .../HtmlGenerationTest.cs | 15 +----- .../Infrastructure/IHtmlDocumentExtensions.cs | 43 +++++++++++++++++ .../RazorPagesTest.cs | 21 ++++++++- .../PageModelTest.cs | 46 +++++++++++++++++++ .../PageTest.cs | 46 +++++++++++++++++++ .../RenderPartialWithModel.cs | 15 ++++++ .../RenderPartialWithModel.cshtml | 4 ++ .../RenderPartialWithoutModel.cshtml | 5 ++ .../Views/Shared/_PartialWithModel.cshtml | 3 ++ .../Views/Shared/_PartialWithoutModel.cshtml | 1 + 13 files changed, 240 insertions(+), 37 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs create mode 100644 test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs create mode 100644 test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs index b23063eeb0..3dda563ebb 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs @@ -1214,6 +1214,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public virtual UnauthorizedResult Unauthorized() => new UnauthorizedResult(); + /// + /// Creates a by specifying the name of a partial to render. + /// + /// The partial name. + /// The created object for the response. + public virtual PartialViewResult Partial(string viewName) + { + return Partial(viewName, model: null); + } + + /// + /// Creates a by specifying the name of a partial to render and the model object. + /// + /// The partial name. + /// The model to be passed into the partial. + /// The created object for the response. + public virtual PartialViewResult Partial(string viewName, object model) + { + ViewContext.ViewData.Model = model; + + return new PartialViewResult + { + ViewName = viewName, + ViewData = ViewContext.ViewData + }; + } + #region ViewComponentResult /// /// Creates a by specifying the name of a view component to render. diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs index 526d0fad72..17384ca1ce 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs @@ -1614,6 +1614,33 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages public virtual UnauthorizedResult Unauthorized() => new UnauthorizedResult(); + /// + /// Creates a by specifying the name of a partial to render. + /// + /// The partial name. + /// The created object for the response. + public virtual PartialViewResult Partial(string viewName) + { + return Partial(viewName, model: null); + } + + /// + /// Creates a by specifying the name of a partial to render and the model object. + /// + /// The partial name. + /// The model to be passed into the partial. + /// The created object for the response. + public virtual PartialViewResult Partial(string viewName, object model) + { + ViewData.Model = model; + + return new PartialViewResult + { + ViewName = viewName, + ViewData = ViewData + }; + } + #region ViewComponentResult /// /// Creates a by specifying the name of a view component to render. diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs index 43bd304d14..dd3c01c170 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs @@ -1,10 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Linq; using System.Net.Http; -using AngleSharp.Dom.Html; using AngleSharp.Parser.Html; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -19,27 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var parser = new HtmlParser(); var htmlDocument = parser.Parse(htmlContent); - return RetrieveAntiforgeryToken(htmlDocument); - } - - public static string RetrieveAntiforgeryToken(IHtmlDocument htmlDocument) - { - var hiddenInputs = htmlDocument.QuerySelectorAll("form input[type=hidden]"); - foreach (var input in hiddenInputs) - { - if (!input.HasAttribute("name")) - { - continue; - } - - var name = input.GetAttribute("name"); - if (name == "__RequestVerificationToken" || name == "HtmlEncode[[__RequestVerificationToken]]") - { - return input.GetAttribute("value"); - } - } - - throw new Exception($"Antiforgery token could not be located in {htmlDocument.Source.Text}."); + return htmlDocument.RetrieveAntiforgeryToken(); } public static CookieMetadata RetrieveAntiforgeryCookie(HttpResponseMessage response) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index f865f1bcd4..e0ce243842 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -566,7 +566,7 @@ Products: Music Systems, Televisions (3)"; var document = await Client.GetHtmlDocumentAsync(url); // Assert - var banner = QuerySelector(document, ".banner"); + var banner = document.RequiredQuerySelector(".banner"); Assert.Equal("Some status message", banner.TextContent); } @@ -581,21 +581,10 @@ Products: Music Systems, Televisions (3)"; var document = await Client.GetHtmlDocumentAsync(url); // Assert - var banner = QuerySelector(document, ".banner"); + var banner = document.RequiredQuerySelector(".banner"); Assert.Empty(banner.TextContent); } - private static IElement QuerySelector(IHtmlDocument document, string selector) - { - var element = document.QuerySelector(selector); - if (element == null) - { - throw new ArgumentException($"Document does not contain element that matches the selector {selector}: " + Environment.NewLine + document.DocumentElement.OuterHtml); - } - - return element; - } - private static HttpRequestMessage RequestWithLocale(string url, string locale) { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs new file mode 100644 index 0000000000..b23ccfa657 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using AngleSharp.Dom; +using AngleSharp.Dom.Html; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public static class IHtmlDocumentExtensions + { + public static IElement RequiredQuerySelector(this IHtmlDocument document, string selector) + { + var element = document.QuerySelector(selector); + if (element == null) + { + throw new ArgumentException($"Document does not contain element that matches the selector {selector}: " + Environment.NewLine + document.DocumentElement.OuterHtml); + } + + return element; + } + + public static string RetrieveAntiforgeryToken(this IHtmlDocument htmlDocument) + { + var hiddenInputs = htmlDocument.QuerySelectorAll("form input[type=hidden]"); + foreach (var input in hiddenInputs) + { + if (!input.HasAttribute("name")) + { + continue; + } + + var name = input.GetAttribute("name"); + if (name == "__RequestVerificationToken" || name == "HtmlEncode[[__RequestVerificationToken]]") + { + return input.GetAttribute("value"); + } + } + + throw new Exception($"Antiforgery token could not be located in {htmlDocument.Source.Text}."); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index d97e128727..88be27634f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -11,7 +11,6 @@ using System.Net.Http.Headers; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Testing; using Newtonsoft.Json.Linq; @@ -144,6 +143,26 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("CustomActionResult", content); } + [Fact] + public async Task Page_Handler_ReturnPartialWithoutModel() + { + // Act + var document = await Client.GetHtmlDocumentAsync("RenderPartialWithoutModel"); + + var element = document.RequiredQuerySelector("#content"); + Assert.Equal("Welcome, Guest", element.TextContent); + } + + [Fact] + public async Task Page_Handler_ReturnPartialWithModel() + { + // Act + var document = await Client.GetHtmlDocumentAsync("RenderPartialWithModel"); + + var element = document.RequiredQuerySelector("#content"); + Assert.Equal("Welcome, Admin", element.TextContent); + } + [Fact] public async Task Page_Handler_AsyncReturnTypeImplementsIActionResult() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index b3443d36c1..3decf16891 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -1894,6 +1894,52 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages testPageModel.Verify(); } + [Fact] + public void PartialView_WithName() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPageModel + { + PageContext = new PageContext + { + ViewData = viewData + } + }; + + // Act + var result = pageModel.Partial("LoginStatus"); + + // Assert + Assert.NotNull(result); + Assert.Equal("LoginStatus", result.ViewName); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void PartialView_WithNameAndModel() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPageModel + { + PageContext = new PageContext + { + ViewData = viewData + } + }; + var model = new { Username = "Admin" }; + + // Act + var result = pageModel.Partial("LoginStatus", model); + + // Assert + Assert.NotNull(result); + Assert.Equal("LoginStatus", result.ViewName); + Assert.Equal(model, result.Model); + Assert.Same(viewData, result.ViewData); + } + [Fact] public void ViewComponent_WithName() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs index 60d9cc7884..2e4e50cf91 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs @@ -1697,6 +1697,52 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages Assert.Equal(statusCode, result.StatusCode); } + [Fact] + public void PartialView_WithName() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPage + { + ViewContext = new ViewContext + { + ViewData = viewData + } + }; + + // Act + var result = pageModel.Partial("LoginStatus"); + + // Assert + Assert.NotNull(result); + Assert.Equal("LoginStatus", result.ViewName); + Assert.Same(viewData, result.ViewData); + } + + [Fact] + public void PartialView_WithNameAndModel() + { + // Arrange + var viewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary()); + var pageModel = new TestPage + { + ViewContext = new ViewContext + { + ViewData = viewData + } + }; + var model = new { Username = "Admin" }; + + // Act + var result = pageModel.Partial("LoginStatus", model); + + // Assert + Assert.NotNull(result); + Assert.Equal("LoginStatus", result.ViewName); + Assert.Equal(model, result.Model); + Assert.Same(viewData, result.ViewData); + } + [Fact] public void ViewComponent_WithName() { diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs b/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs new file mode 100644 index 0000000000..82711aed2e --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs @@ -0,0 +1,15 @@ +// 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; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite +{ + public class RenderPartialWithModel : PageModel + { + public IActionResult OnGet() => Partial("_PartialWithModel", this); + + public string Username => "Admin"; + } +} diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml b/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml new file mode 100644 index 0000000000..27d507ed75 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml @@ -0,0 +1,4 @@ +@page +@model RazorPagesWebSite.RenderPartialWithModel + +

The partial will be loaded here ...

\ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml b/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml new file mode 100644 index 0000000000..b817a76e67 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml @@ -0,0 +1,5 @@ +@page + +@functions { + public IActionResult OnGet() => Partial("_PartialWithoutModel"); +} diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml b/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml new file mode 100644 index 0000000000..5c8194f423 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml @@ -0,0 +1,3 @@ +@model RazorPagesWebSite.RenderPartialWithModel + +Welcome, @Model.Username \ No newline at end of file diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml b/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml new file mode 100644 index 0000000000..40b6bbaf56 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml @@ -0,0 +1 @@ +Welcome, Guest \ No newline at end of file From dc2ae93c3f37ff7f44c2d09f68065b19db6a3245 Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Sun, 1 Jul 2018 16:29:54 -0700 Subject: [PATCH 090/316] Add fallback attribute to partial tag helper. Addresses #7515 --- .../PartialTagHelper.cs | 75 ++++--- .../Properties/Resources.Designer.cs | 14 ++ .../Resources.resx | 3 + .../HtmlGenerationTest.cs | 28 +++ .../PartialTagHelperTest.cs | 192 ++++++++++++++++++ .../Customer/Pages/PartialWithFallback.cshtml | 3 + .../Customer/Pages/PartialWithOptional.cshtml | 3 + .../Areas/Customer/Pages/_Fallback.cshtml | 1 + .../Areas/Customer/Pages/_ViewImports.cshtml | 1 + 9 files changed, 295 insertions(+), 25 deletions(-) create mode 100644 test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml create mode 100644 test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml create mode 100644 test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml create mode 100644 test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs index fef6b7c0e8..617fa5d3a2 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { private const string ForAttributeName = "for"; private const string ModelAttributeName = "model"; + private const string FallbackAttributeName = "fallback-name"; private const string OptionalAttributeName = "optional"; private object _model; private bool _hasModel; @@ -82,6 +83,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(OptionalAttributeName)] public bool Optional { get; set; } + /// + /// View to lookup if the view specified by cannot be located. + /// + [HtmlAttributeName(FallbackAttributeName)] + public string FallbackName { get; set; } + /// /// A to pass into the partial view. /// @@ -104,21 +111,46 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers throw new ArgumentNullException(nameof(context)); } - var viewEngineResult = FindView(); - - if (viewEngineResult.Success) - { - var model = ResolveModel(); - var viewBuffer = new ViewBuffer(_viewBufferScope, Name, ViewBuffer.PartialViewPageSize); - using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8)) - { - await RenderPartialViewAsync(writer, model, viewEngineResult.View); - output.Content.SetHtmlContent(viewBuffer); - } - } - // Reset the TagName. We don't want `partial` to render. output.TagName = null; + + var result = FindView(Name); + var viewSearchedLocations = result.SearchedLocations; + var fallBackViewSearchedLocations = Enumerable.Empty(); + + if (!result.Success && !string.IsNullOrEmpty(FallbackName)) + { + result = FindView(FallbackName); + fallBackViewSearchedLocations = result.SearchedLocations; + } + + if (!result.Success) + { + if (Optional) + { + // Could not find the view or fallback view, but the partial is marked as optional. + return; + } + + var locations = Environment.NewLine + string.Join(Environment.NewLine, viewSearchedLocations); + var errorMessage = Resources.FormatViewEngine_PartialViewNotFound(Name, locations); + + if (!string.IsNullOrEmpty(FallbackName)) + { + locations = Environment.NewLine + string.Join(Environment.NewLine, result.SearchedLocations); + errorMessage += Environment.NewLine + Resources.FormatViewEngine_FallbackViewNotFound(FallbackName, locations); + } + + throw new InvalidOperationException(errorMessage); + } + + var model = ResolveModel(); + var viewBuffer = new ViewBuffer(_viewBufferScope, result.ViewName, ViewBuffer.PartialViewPageSize); + using (var writer = new ViewBufferTextWriter(viewBuffer, Encoding.UTF8)) + { + await RenderPartialViewAsync(writer, model, result.View); + output.Content.SetHtmlContent(viewBuffer); + } } // Internal for testing @@ -152,26 +184,19 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return ViewContext.ViewData.Model; } - private ViewEngineResult FindView() + private ViewEngineResult FindView(string partialName) { - var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, Name, isMainPage: false); + var viewEngineResult = _viewEngine.GetView(ViewContext.ExecutingFilePath, partialName, isMainPage: false); var getViewLocations = viewEngineResult.SearchedLocations; if (!viewEngineResult.Success) { - viewEngineResult = _viewEngine.FindView(ViewContext, Name, isMainPage: false); + viewEngineResult = _viewEngine.FindView(ViewContext, partialName, isMainPage: false); } - if (!viewEngineResult.Success && !Optional) + if (!viewEngineResult.Success) { var searchedLocations = Enumerable.Concat(getViewLocations, viewEngineResult.SearchedLocations); - var locations = string.Empty; - if (searchedLocations.Any()) - { - locations += Environment.NewLine + string.Join(Environment.NewLine, searchedLocations); - } - - throw new InvalidOperationException( - Resources.FormatViewEngine_PartialViewNotFound(Name, locations)); + return ViewEngineResult.NotFound(partialName, searchedLocations); } return viewEngineResult; diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs index 93ac7fa60e..f5b5d8c690 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs @@ -206,6 +206,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers internal static string FormatPartialTagHelper_InvalidModelAttributes(object p0, object p1, object p2) => string.Format(CultureInfo.CurrentCulture, GetString("PartialTagHelper_InvalidModelAttributes"), p0, p1, p2); + /// + /// The fallback partial view '{0}' was not found. The following locations were searched:{1} + /// + internal static string ViewEngine_FallbackViewNotFound + { + get => GetString("ViewEngine_FallbackViewNotFound"); + } + + /// + /// The fallback partial view '{0}' was not found. The following locations were searched:{1} + /// + internal static string FormatViewEngine_FallbackViewNotFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ViewEngine_FallbackViewNotFound"), p0, p1); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx index 1d6166e955..96c56dff7f 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx @@ -159,4 +159,7 @@ Cannot use '{0}' with both '{1}' and '{2}' attributes. + + The fallback partial view '{0}' was not found. The following locations were searched:{1} + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index e0ce243842..32cb029485 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -585,6 +585,34 @@ Products: Music Systems, Televisions (3)"; Assert.Empty(banner.TextContent); } + [Fact] + public async Task PartialTagHelper_AllowsUsingFallback() + { + // Arrange + var url = "/Customer/PartialWithFallback"; + + // Act + var document = await Client.GetHtmlDocumentAsync(url); + + // Assert + var content = document.RequiredQuerySelector("#content"); + Assert.Equal("Hello from fallback", content.TextContent); + } + + [Fact] + public async Task PartialTagHelper_AllowsUsingOptional() + { + // Arrange + var url = "/Customer/PartialWithOptional"; + + // Act + var document = await Client.GetHtmlDocumentAsync(url); + + // Assert + var content = document.RequiredQuerySelector("#content"); + Assert.Empty(content.TextContent); + } + private static HttpRequestMessage RequestWithLocale(string url, string locale) { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs index 21edd86f7d..489c78bd05 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs @@ -648,6 +648,198 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Empty(content); } + [Fact] + public async Task ProcessAsync_RendersMainPartial_If_FallbackIsSet_AndMainPartialIsFound() + { + // Arrange + var expected = "Hello from partial!"; + var bufferScope = new TestViewBufferScope(); + var partialName = "_Partial"; + var fallbackName = "_Fallback"; + var model = new object(); + var viewContext = GetViewContext(); + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + v.Writer.Write(expected); + }) + .Returns(Task.CompletedTask); + + var fallbackView = new Mock(); + fallbackView.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + v.Writer.Write("Hello from fallback partial!"); + }) + .Returns(Task.CompletedTask); + + var viewEngine = new Mock(); + viewEngine.Setup(v => v.GetView(It.IsAny(), partialName, false)) + .Returns(ViewEngineResult.Found(partialName, view.Object)); + viewEngine.Setup(v => v.GetView(It.IsAny(), fallbackName, false)) + .Returns(ViewEngineResult.Found(fallbackName, fallbackView.Object)); + + var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope) + { + Name = partialName, + ViewContext = viewContext, + FallbackName = fallbackName + }; + var tagHelperContext = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act + await tagHelper.ProcessAsync(tagHelperContext, output); + + // Assert + var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder()); + Assert.Equal(expected, content); + } + + [Fact] + public async Task ProcessAsync_IfHasFallback_Throws_When_MainPartialAndFallback_AreNotFound() + { + // Arrange + var bufferScope = new TestViewBufferScope(); + var partialName = "_Partial"; + var fallbackName = "_Fallback"; + var expected = string.Join( + Environment.NewLine, + $"The partial view '{partialName}' was not found. The following locations were searched:", + "PartialNotFound1", + "PartialNotFound2", + "PartialNotFound3", + "PartialNotFound4", + $"The fallback partial view '{fallbackName}' was not found. The following locations were searched:", + "FallbackNotFound1", + "FallbackNotFound2", + "FallbackNotFound3", + "FallbackNotFound4"); + var viewData = new ViewDataDictionary(new TestModelMetadataProvider(), new ModelStateDictionary()); + var viewContext = GetViewContext(); + + var view = Mock.Of(); + var viewEngine = new Mock(); + viewEngine.Setup(v => v.GetView(It.IsAny(), partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, new[] { "PartialNotFound1", "PartialNotFound2" })); + + viewEngine.Setup(v => v.FindView(viewContext, partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, new[] { $"PartialNotFound3", $"PartialNotFound4" })); + + viewEngine.Setup(v => v.GetView(It.IsAny(), fallbackName, false)) + .Returns(ViewEngineResult.NotFound(partialName, new[] { "FallbackNotFound1", "FallbackNotFound2" })); + + viewEngine.Setup(v => v.FindView(viewContext, fallbackName, false)) + .Returns(ViewEngineResult.NotFound(partialName, new[] { $"FallbackNotFound3", $"FallbackNotFound4" })); + + var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope) + { + Name = partialName, + ViewContext = viewContext, + ViewData = viewData, + FallbackName = fallbackName + }; + var tagHelperContext = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act & Assert + var exception = await Assert.ThrowsAsync( + () => tagHelper.ProcessAsync(tagHelperContext, output)); + Assert.Equal(expected, exception.Message); + } + + [Fact] + public async Task ProcessAsync_RendersFallbackView_If_MainIsNotFound_AndGetViewReturnsView() + { + // Arrange + var expected = "Hello from fallback!"; + var bufferScope = new TestViewBufferScope(); + var partialName = "_Partial"; + var fallbackName = "_Fallback"; + var model = new object(); + var viewContext = GetViewContext(); + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + v.Writer.Write(expected); + }) + .Returns(Task.CompletedTask); + + var viewEngine = new Mock(); + viewEngine.Setup(v => v.GetView(It.IsAny(), partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, Array.Empty())); + viewEngine.Setup(v => v.FindView(viewContext, partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, Array.Empty())); + viewEngine.Setup(v => v.GetView(It.IsAny(), fallbackName, false)) + .Returns(ViewEngineResult.Found(fallbackName, view.Object)); + + var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope) + { + Name = partialName, + ViewContext = viewContext, + FallbackName = fallbackName + }; + var tagHelperContext = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act + await tagHelper.ProcessAsync(tagHelperContext, output); + + // Assert + var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder()); + Assert.Equal(expected, content); + } + + [Fact] + public async Task ProcessAsync_RendersFallbackView_If_MainIsNotFound_AndFindViewReturnsView() + { + // Arrange + var expected = "Hello from fallback!"; + var bufferScope = new TestViewBufferScope(); + var partialName = "_Partial"; + var fallbackName = "_Fallback"; + var model = new object(); + var viewContext = GetViewContext(); + + var view = new Mock(); + view.Setup(v => v.RenderAsync(It.IsAny())) + .Callback((ViewContext v) => + { + v.Writer.Write(expected); + }) + .Returns(Task.CompletedTask); + + var viewEngine = new Mock(); + viewEngine.Setup(v => v.GetView(It.IsAny(), partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, Array.Empty())); + viewEngine.Setup(v => v.FindView(viewContext, partialName, false)) + .Returns(ViewEngineResult.NotFound(partialName, Array.Empty())); + viewEngine.Setup(v => v.GetView(It.IsAny(), fallbackName, false)) + .Returns(ViewEngineResult.NotFound(fallbackName, Array.Empty())); + viewEngine.Setup(v => v.FindView(viewContext, fallbackName, false)) + .Returns(ViewEngineResult.Found(fallbackName, view.Object)); + + var tagHelper = new PartialTagHelper(viewEngine.Object, bufferScope) + { + Name = partialName, + ViewContext = viewContext, + FallbackName = fallbackName + }; + var tagHelperContext = GetTagHelperContext(); + var output = GetTagHelperOutput(); + + // Act + await tagHelper.ProcessAsync(tagHelperContext, output); + + // Assert + var content = HtmlContentUtilities.HtmlContentToString(output.Content, new HtmlTestEncoder()); + Assert.Equal(expected, content); + } + private static ViewContext GetViewContext() { return new ViewContext( diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml new file mode 100644 index 0000000000..fc6b995ab1 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml @@ -0,0 +1,3 @@ +@page + + \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml new file mode 100644 index 0000000000..c3c8ecd2cb --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml @@ -0,0 +1,3 @@ +@page + +
\ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml new file mode 100644 index 0000000000..dee9334704 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml @@ -0,0 +1 @@ +Hello from fallback \ No newline at end of file diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..a757b413b9 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml @@ -0,0 +1 @@ +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers From 1e7be641ae62759ecaf83806f48713e0437c1ea3 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 9 Jul 2018 11:13:35 -0700 Subject: [PATCH 091/316] Add some common aliases for conventions Fixes #8015 --- .../DefaultApiConventions.cs | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs index 3508fb98cc..281e78461f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs @@ -8,6 +8,7 @@ namespace Microsoft.AspNetCore.Mvc { public static class DefaultApiConventions { + #region GET [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] @@ -17,6 +18,18 @@ namespace Microsoft.AspNetCore.Mvc [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object id) { } + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Find( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id) + { } + #endregion + + #region POST [ProducesResponseType(StatusCodes.Status201Created)] [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType] @@ -26,6 +39,18 @@ namespace Microsoft.AspNetCore.Mvc [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object model) { } + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Create( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object model) + { } + #endregion + + #region PUT [ProducesResponseType(StatusCodes.Status204NoContent)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -40,6 +65,38 @@ namespace Microsoft.AspNetCore.Mvc [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object model) { } + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Edit( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id, + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object model) + { } + + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Update( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object id, + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] + object model) + { } + #endregion + + #region DELETE [ProducesResponseType(StatusCodes.Status200OK)] [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesResponseType(StatusCodes.Status400BadRequest)] @@ -49,5 +106,6 @@ namespace Microsoft.AspNetCore.Mvc [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Suffix)] [ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] object id) { } + #endregion } } From 183ecd85d6b4841f43c882a0122e6fa90dda841e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 11 Jul 2018 11:47:33 +1200 Subject: [PATCH 092/316] Fix MVC integration with UseEndpoint (#8047) --- .../MvcApplicationBuilderExtensions.cs | 1 - .../Internal/MvcEndpointDataSource.cs | 25 ++++- .../Internal/MvcEndpointDataSourceTests.cs | 76 ++++++------- .../RouteDataTest.cs | 103 ------------------ .../RoutingTestsBase.cs | 80 ++++++++++++++ .../Controllers/RouteDataController.cs} | 6 +- 6 files changed, 144 insertions(+), 147 deletions(-) delete mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RouteDataTest.cs rename test/WebSites/{BasicWebSite/Controllers/RoutingController.cs => RoutingWebSite/Controllers/RouteDataController.cs} (90%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 8e0ed3bd2f..c35d40d0f1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -122,7 +122,6 @@ namespace Microsoft.AspNetCore.Builder configureRoutes(routeBuilder); mvcEndpointDataSource.ConventionalEndpointInfos.AddRange(routeBuilder.EndpointInfos); - mvcEndpointDataSource.InitializeEndpoints(); return app.UseEndpoint(); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index dab81b662b..786ce62b61 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -24,8 +24,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal private readonly IServiceProvider _serviceProvider; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; private readonly List _endpoints; + private readonly object _lock = new object(); private IChangeToken _changeToken; + private bool _initialized; public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, @@ -62,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal ConventionalEndpointInfos = new List(); } - public void InitializeEndpoints() + private void InitializeEndpoints() { foreach (var action in _actions.ActionDescriptors.Items) { @@ -387,8 +389,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - public override IReadOnlyList Endpoints => _endpoints; + public override IReadOnlyList Endpoints + { + get + { + if (!_initialized) + { + lock (_lock) + { + if (!_initialized) + { + InitializeEndpoints(); + _initialized = true; + } + } + } + return _endpoints; + } + } + + // REVIEW: Infos added after endpoints are initialized will not be used public List ConventionalEndpointInfos { get; } private class RouteNameMetadata : IRouteNameMetadata diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index f3b6d778df..635169ee32 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -59,10 +59,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal var dataSource = CreateMvcEndpointDataSource(mockDescriptorProvider.Object); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); var endpointValue = matcherEndpoint.RequiredValues["Name"]; @@ -115,10 +115,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object)); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask); @@ -192,7 +192,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert var inspectors = finalEndpointTemplates @@ -200,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal .ToArray(); // Assert - Assert.Collection(dataSource.Endpoints, inspectors); + Assert.Collection(endpoints, inspectors); } [Theory] @@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert var inspectors = finalEndpointTemplates @@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal .ToArray(); // Assert - Assert.Collection(dataSource.Endpoints, inspectors); + Assert.Collection(endpoints, inspectors); } [Fact] @@ -243,10 +243,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal new RouteValueDictionary(new { action = "TestAction" }))); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Collection(dataSource.Endpoints, + Assert.Collection(endpoints, (e) => Assert.Equal("TestController", Assert.IsType(e).Template), (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); } @@ -266,10 +266,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal constraints: new RouteValueDictionary(new { action = "(TestAction1|TestAction2)" }))); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Collection(dataSource.Endpoints, + Assert.Collection(endpoints, (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).Template), (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).Template)); } @@ -291,14 +291,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfoRoute)); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; var inspectors = finalEndpointTemplates .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) .ToArray(); // Assert - Assert.Collection(dataSource.Endpoints, inspectors); + Assert.Collection(endpoints, inspectors); } [Fact] @@ -312,10 +312,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal CreateEndpointInfo(string.Empty, "named/{controller}/{action}/{id?}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.Null(routeNameMetadata); @@ -333,11 +333,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal CreateEndpointInfo("namedRoute", "named/{controller}/{action}/{id?}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert Assert.Collection( - dataSource.Endpoints, + endpoints, (ep) => { var matcherEndpoint = Assert.IsType(ep); @@ -372,11 +372,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal "named/{controller}/{action}/{id?}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert Assert.Collection( - dataSource.Endpoints, + endpoints, (ep) => { var matcherEndpoint = Assert.IsType(ep); @@ -413,10 +413,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(dataSource.Endpoints); + Assert.Empty(endpoints); } // Since area, controller, action and page are special, check to see if the followin test succeeds for a @@ -431,10 +431,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(dataSource.Endpoints); + Assert.Empty(endpoints); } [Fact] @@ -447,10 +447,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(dataSource.Endpoints); + Assert.Empty(endpoints); } [Fact] @@ -463,10 +463,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area=admin}/{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(dataSource.Endpoints); + Assert.Empty(endpoints); } [Fact] @@ -479,10 +479,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(dataSource.Endpoints); + Assert.Empty(endpoints); } [Fact] @@ -495,10 +495,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.Template); AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); @@ -517,10 +517,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.Template); AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); @@ -540,10 +540,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}/{subscription=general}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.Template); AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); @@ -562,10 +562,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); // Act - dataSource.InitializeEndpoints(); + var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(dataSource.Endpoints); + var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.Template); AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RouteDataTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RouteDataTest.cs deleted file mode 100644 index 3ea689b1a4..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RouteDataTest.cs +++ /dev/null @@ -1,103 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Routing; -using Newtonsoft.Json; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.FunctionalTests -{ - public class RouteDataTest : IClassFixture> - { - public RouteDataTest(MvcTestFixture fixture) - { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact] - public async Task RouteData_Routers_ConventionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Routing/Conventional"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal( - new string[] - { - typeof(RouteCollection).FullName, - typeof(Route).FullName, - typeof(MvcRouteHandler).FullName, - }, - result.Routers); - } - - [Fact] - public async Task RouteData_Routers_AttributeRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/Routing/Attribute"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal(new string[] - { - typeof(RouteCollection).FullName, - typeof(AttributeRoute).FullName, - typeof(MvcAttributeRouteHandler).FullName, - }, - result.Routers); - } - - // Verifies that components in the MVC pipeline can modify datatokens - // without impacting any static data. - // - // This does two request, to verify that the data in the route is not modified - [Fact] - public async Task RouteData_DataTokens_FilterCanSetDataTokens() - { - // Arrange - var response = await Client.GetAsync("http://localhost/Routing/DataTokens"); - - // Guard - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - Assert.Single(result.DataTokens); - Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "DataTokens"); - - // Act - response = await Client.GetAsync("http://localhost/Routing/Conventional"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - body = await response.Content.ReadAsStringAsync(); - result = JsonConvert.DeserializeObject(body); - - Assert.Single(result.DataTokens); - Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "Conventional"); - } - - private class ResultData - { - public Dictionary DataTokens { get; set; } - - public string[] Routers { get; set; } - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index a5ea33a0f1..99c2710762 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Newtonsoft.Json; using Xunit; @@ -22,6 +23,85 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public async Task RouteData_Routers_ConventionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Conventional"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal( + new string[] + { + typeof(RouteCollection).FullName, + typeof(Route).FullName, + typeof(MvcRouteHandler).FullName, + }, + result.Routers); + } + + [Fact] + public async Task RouteData_Routers_AttributeRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Attribute"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal(new string[] + { + typeof(RouteCollection).FullName, + typeof(AttributeRoute).FullName, + typeof(MvcAttributeRouteHandler).FullName, + }, + result.Routers); + } + + // Verifies that components in the MVC pipeline can modify datatokens + // without impacting any static data. + // + // This does two request, to verify that the data in the route is not modified + [Fact] + public async Task RouteData_DataTokens_FilterCanSetDataTokens() + { + // Arrange + var response = await Client.GetAsync("http://localhost/RouteData/DataTokens"); + + // Guard + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + Assert.Single(result.DataTokens); + Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "DataTokens"); + + // Act + response = await Client.GetAsync("http://localhost/RouteData/Conventional"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + body = await response.Content.ReadAsStringAsync(); + result = JsonConvert.DeserializeObject(body); + + Assert.Single(result.DataTokens); + Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "Conventional"); + } + + private class ResultData + { + public Dictionary DataTokens { get; set; } + + public string[] Routers { get; set; } + } + [Fact] public virtual async Task ConventionalRoutedController_ActionIsReachable() { diff --git a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs similarity index 90% rename from test/WebSites/BasicWebSite/Controllers/RoutingController.cs rename to test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs index c5576b4685..e02f385707 100644 --- a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs @@ -6,16 +6,16 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; -namespace BasicWebSite +namespace RoutingWebSite.Controllers { - public class RoutingController : Controller + public class RouteDataController : Controller { public object Conventional() { return GetData(); } - [Route("Routing/Attribute")] + [Route("RouteData/Attribute")] public object Attribute() { return GetData(); From c367e1d681659f89d8ff1b10a0e300a1960c505b Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Jul 2018 14:19:48 +1200 Subject: [PATCH 093/316] Fix dispatching functional tests not using dispatching startup (#8052) --- .../DispatchingTests.cs | 63 +++++++++++++++++++ .../RoutingTests.cs | 50 +++++++++++++++ .../RoutingTestsBase.cs | 50 +++------------ 3 files changed, 122 insertions(+), 41 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs index 3f8f012c57..05525fc755 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -1,7 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Net; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -12,5 +17,63 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea() + { + return Task.CompletedTask; + } + + [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + { + return Task.CompletedTask; + } + + [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() + { + return Task.CompletedTask; + } + + [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + public override Task ConventionalRoutedAction_InArea_StaysInArea() + { + return Task.CompletedTask; + } + + [Fact] + public async override Task RouteData_Routers_ConventionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Conventional"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal( + Array.Empty(), + result.Routers); + } + + [Fact] + public async override Task RouteData_Routers_AttributeRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Attribute"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal( + Array.Empty(), + result.Routers); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index 3a3e6527dd..e4c3f088ea 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -1,6 +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.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class RoutingTests : RoutingTestsBase @@ -9,5 +16,48 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task RouteData_Routers_ConventionalRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Conventional"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal( + new string[] + { + typeof(RouteCollection).FullName, + typeof(Route).FullName, + typeof(MvcRouteHandler).FullName, + }, + result.Routers); + } + + [Fact] + public async override Task RouteData_Routers_AttributeRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/RouteData/Attribute"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal(new string[] + { + typeof(RouteCollection).FullName, + typeof(AttributeRoute).FullName, + typeof(MvcAttributeRouteHandler).FullName, + }, + result.Routers); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 99c2710762..20918254b9 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Newtonsoft.Json; @@ -18,53 +19,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { protected RoutingTestsBase(MvcTestFixture fixture) { - Client = fixture.CreateDefaultClient(); + var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); + Client = factory.CreateDefaultClient(); } + private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => + builder.UseStartup(); + public HttpClient Client { get; } [Fact] - public async Task RouteData_Routers_ConventionalRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/RouteData/Conventional"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal( - new string[] - { - typeof(RouteCollection).FullName, - typeof(Route).FullName, - typeof(MvcRouteHandler).FullName, - }, - result.Routers); - } + public abstract Task RouteData_Routers_ConventionalRoute(); [Fact] - public async Task RouteData_Routers_AttributeRoute() - { - // Arrange & Act - var response = await Client.GetAsync("http://localhost/RouteData/Attribute"); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal(new string[] - { - typeof(RouteCollection).FullName, - typeof(AttributeRoute).FullName, - typeof(MvcAttributeRouteHandler).FullName, - }, - result.Routers); - } + public abstract Task RouteData_Routers_AttributeRoute(); // Verifies that components in the MVC pipeline can modify datatokens // without impacting any static data. @@ -95,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Single(result.DataTokens, kvp => kvp.Key == "actionName" && ((string)kvp.Value) == "Conventional"); } - private class ResultData + protected class ResultData { public Dictionary DataTokens { get; set; } From a67d9363e22be8ef63a1a62539991e1da3a6e30e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 12 Jul 2018 16:35:33 +1200 Subject: [PATCH 094/316] Consumes endpoint constraint (#8057) --- .../ConsumesAttribute.cs | 97 +++++++- .../Internal/IConsumesActionConstraint.cs | 9 + .../ConsumesAttributeTests.cs | 228 ++++++++++++++++-- .../ConsumesAttributeDispatchingTests.cs | 13 + .../ConsumesAttributeTests.cs | 160 +----------- .../ConsumesAttributeTestsBase.cs | 175 ++++++++++++++ .../BasicWebSite/StartupWithDispatching.cs | 37 +++ 7 files changed, 546 insertions(+), 173 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs create mode 100644 test/WebSites/BasicWebSite/StartupWithDispatching.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs index c3c240e63e..7d63adcabf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -11,6 +11,8 @@ 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; namespace Microsoft.AspNetCore.Mvc @@ -24,7 +26,8 @@ namespace Microsoft.AspNetCore.Mvc Attribute, IResourceFilter, IConsumesActionConstraint, - IApiRequestMetadataProvider + IApiRequestMetadataProvider, + IConsumesEndpointConstraint { public static readonly int ConsumesActionConstraintOrder = 200; @@ -55,6 +58,11 @@ namespace Microsoft.AspNetCore.Mvc /// int IActionConstraint.Order => ConsumesActionConstraintOrder; + // The value used is a non default value so that it avoids getting mixed with other endpoint constraints + // with default order. + /// + int IEndpointConstraint.Order => ConsumesActionConstraintOrder; + /// /// Gets or sets the supported request content types. Used to select an action when there would otherwise be /// multiple matches. @@ -184,6 +192,83 @@ namespace Microsoft.AspNetCore.Mvc return true; } + /// + public bool Accept(EndpointConstraintContext context) + { + // If this constraint is not closest to the endpoint, it will be skipped. + if (!IsApplicable(context.CurrentCandidate.Endpoint)) + { + // Since the constraint is to be skipped, returning true here + // will let the current candidate ignore this constraint and will + // be selected based on other constraints for this endpoint. + return true; + } + + var requestContentType = context.HttpContext.Request.ContentType; + + // If the request content type is null we need to act like pass through. + // In case there is a single candidate with a constraint it should be selected. + // If there are multiple endpoints with consumes endpoint constraints this should result in ambiguous exception + // unless there is another endpoint without a consumes constraint. + if (requestContentType == null) + { + var isEndpointWithoutConsumeConstraintPresent = context.Candidates.Any( + candidate => candidate.Constraints == null || + !candidate.Constraints.Any(constraint => constraint is IConsumesEndpointConstraint)); + + return !isEndpointWithoutConsumeConstraintPresent; + } + + // Confirm the request's content type is more specific than (a media type this endpoint supports e.g. OK + // if client sent "text/plain" data and this endpoint supports "text/*". + if (IsSubsetOfAnyContentType(requestContentType)) + { + return true; + } + + var firstCandidate = context.Candidates[0]; + if (firstCandidate.Endpoint != context.CurrentCandidate.Endpoint) + { + // If the current candidate is not same as the first candidate, + // we need not probe other candidates to see if they apply. + // Only the first candidate is allowed to probe other candidates and based on the result select itself. + return false; + } + + // Run the matching logic for all IConsumesEndpointConstraints we can find, and see what matches. + // 1). If we have a unique best match, then only that constraint should return true. + // 2). If we have multiple matches, then all constraints that match will return true + // , resulting in ambiguity(maybe). + // 3). If we have no matches, then we choose the first constraint to return true.It will later return a 415 + foreach (var candidate in context.Candidates) + { + if (candidate.Endpoint == firstCandidate.Endpoint) + { + continue; + } + + var tempContext = new EndpointConstraintContext() + { + Candidates = context.Candidates, + HttpContext = context.HttpContext, + CurrentCandidate = candidate + }; + + if (candidate.Constraints == null || candidate.Constraints.Count == 0 || + candidate.Constraints.Any(constraint => constraint is IConsumesEndpointConstraint && + constraint.Accept(tempContext))) + { + // There is someone later in the chain which can handle the request. + // end the process here. + return false; + } + } + + // There is no one later in the chain that can handle this content type return a false positive so that + // later we can detect and return a 415. + return true; + } + private bool IsApplicable(ActionDescriptor actionDescriptor) { // If there are multiple IConsumeActionConstraints which are defined at the class and @@ -193,7 +278,17 @@ namespace Microsoft.AspNetCore.Mvc // closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this. return actionDescriptor.FilterDescriptors.Last( filter => filter.Filter is IConsumesActionConstraint).Filter == this; + } + private bool IsApplicable(Endpoint endpoint) + { + // If there are multiple IConsumeActionConstraints which are defined at the class and + // at the action level, the one closest to the action overrides the others. To ensure this + // we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an + // IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one + // closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this. + return endpoint.Metadata.Last( + metadata => metadata is IConsumesEndpointConstraint) == this; } private MediaTypeCollection GetContentTypes(string firstArg, string[] args) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs index b004cbdcf3..2806bd496d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace Microsoft.AspNetCore.Mvc.Internal { @@ -12,4 +13,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal public interface IConsumesActionConstraint : IActionConstraint { } + + /// + /// An constraint that identifies a type which can be used to select an action + /// based on incoming request. + /// + public interface IConsumesEndpointConstraint : IEndpointConstraint + { + } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs index a2738ed9a2..83bae7b4b0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs @@ -12,6 +12,8 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.EndpointConstraints; +using Microsoft.AspNetCore.Routing.Matchers; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -80,7 +82,7 @@ namespace Microsoft.AspNetCore.Mvc [InlineData("application/json")] [InlineData("application/json;Parameter1=12")] [InlineData("text/xml")] - public void Accept_MatchesForMachingRequestContentType(string contentType) + public void ActionConstraint_Accept_MatchesForMachingRequestContentType(string contentType) { // Arrange var constraint = new ConsumesAttribute("application/json", "text/xml"); @@ -104,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc } [Fact] - public void Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches() + public void ActionConstraint_Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches() { // Arrange var constraint1 = new ConsumesAttribute("application/json", "text/xml"); @@ -114,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; - var constraint2 = new Mock(); + var constraint2 = new Mock(); var action2 = new ActionDescriptor() { FilterDescriptors = @@ -142,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc [InlineData("application/custom")] [InlineData("")] [InlineData(null)] - public void Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType) + public void ActionConstraint_Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json", "text/xml"); @@ -152,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc new List() { new FilterDescriptor(constraint1, FilterScope.Action) } }; - var constraint2 = new Mock(); + var constraint2 = new Mock(); var action2 = new ActionDescriptor() { FilterDescriptors = @@ -179,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc [Theory] [InlineData("")] [InlineData(null)] - public void Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) + public void ActionConstraint_Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json"); @@ -219,7 +221,7 @@ namespace Microsoft.AspNetCore.Mvc [InlineData("application/xml")] [InlineData("application/custom")] [InlineData("invalid/invalid")] - public void Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) + public void ActionConstraint_Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) { // Arrange var actionWithoutConstraint = new ActionDescriptor(); @@ -258,7 +260,7 @@ namespace Microsoft.AspNetCore.Mvc [Theory] [InlineData("")] [InlineData(null)] - public void Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType) + public void ActionConstraint_Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType) { // Arrange var constraint1 = new ConsumesAttribute("application/json"); @@ -293,6 +295,193 @@ namespace Microsoft.AspNetCore.Mvc Assert.True(constraint2.Accept(context)); } + private MatcherEndpoint CreateEndpoint(params IEndpointConstraint[] constraints) + { + EndpointMetadataCollection endpointMetadata = new EndpointMetadataCollection(constraints); + + return new MatcherEndpoint( + (r) => null, + "", + new RouteValueDictionary(), + new RouteValueDictionary(), + 0, + endpointMetadata, + ""); + } + + [Theory] + [InlineData("application/json")] + [InlineData("application/json;Parameter1=12")] + [InlineData("text/xml")] + public void EndpointConstraint_Accept_MatchesForMachingRequestContentType(string contentType) + { + // Arrange + var constraint = new ConsumesAttribute("application/json", "text/xml"); + var endpoint = CreateEndpoint(constraint); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpoint, new [] { constraint }), + }; + + context.CurrentCandidate = context.Candidates[0]; + context.HttpContext = CreateHttpContext(contentType: contentType); + + // Act & Assert + Assert.True(constraint.Accept(context)); + } + + [Fact] + public void EndpointConstraint_Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches() + { + // Arrange + var constraint1 = new ConsumesAttribute("application/json", "text/xml"); + var endpoint1 = CreateEndpoint(constraint1); + + var constraint2 = new Mock(); + var endpoint2 = CreateEndpoint(constraint2.Object); + + constraint2.Setup(o => o.Accept(It.IsAny())) + .Returns(true); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpoint1, new [] { constraint1 }), + new EndpointSelectorCandidate(endpoint2, new [] { constraint2.Object }), + }; + + context.CurrentCandidate = context.Candidates[0]; + context.HttpContext = CreateHttpContext(contentType: "application/custom"); + + // Act & Assert + Assert.False(constraint1.Accept(context)); + } + + [Theory] + [InlineData("application/custom")] + [InlineData("")] + [InlineData(null)] + public void EndpointConstraint_Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType) + { + // Arrange + var constraint1 = new ConsumesAttribute("application/json", "text/xml"); + var endpoint1 = CreateEndpoint(constraint1); + + var constraint2 = new Mock(); + var endpoint2 = CreateEndpoint(constraint2.Object); + + constraint2.Setup(o => o.Accept(It.IsAny())) + .Returns(false); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpoint1, new [] { constraint1 }), + new EndpointSelectorCandidate(endpoint2, new [] { constraint2.Object }), + }; + + context.CurrentCandidate = context.Candidates[0]; + context.HttpContext = CreateHttpContext(contentType: contentType); + + // Act & Assert + Assert.True(constraint1.Accept(context)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void EndpointConstraint_Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) + { + // Arrange + var constraint1 = new ConsumesAttribute("application/json"); + var endpointWithConstraint = CreateEndpoint(constraint1); + + var constraint2 = new ConsumesAttribute("text/xml"); + var endpointWithConstraint2 = CreateEndpoint(constraint2); + + var endpointWithoutConstraint = CreateEndpoint(); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), + new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), + new EndpointSelectorCandidate(endpointWithoutConstraint, new List()), + }; + + context.HttpContext = CreateHttpContext(contentType: contentType); + + // Act & Assert + context.CurrentCandidate = context.Candidates[0]; + Assert.False(constraint1.Accept(context)); + context.CurrentCandidate = context.Candidates[1]; + Assert.False(constraint2.Accept(context)); + } + + [Theory] + [InlineData("application/xml")] + [InlineData("application/custom")] + [InlineData("invalid/invalid")] + public void EndpointConstraint_Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) + { + // Arrange + var endpointWithoutConstraint = CreateEndpoint(); + var constraint1 = new ConsumesAttribute("application/json"); + var endpointWithConstraint = CreateEndpoint(constraint1); + + var constraint2 = new ConsumesAttribute("text/xml"); + var endpointWithConstraint2 = CreateEndpoint(constraint2); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), + new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), + new EndpointSelectorCandidate(endpointWithoutConstraint, new List()), + }; + + context.HttpContext = CreateHttpContext(contentType: contentType); + + // Act & Assert + context.CurrentCandidate = context.Candidates[0]; + Assert.False(constraint1.Accept(context)); + + context.CurrentCandidate = context.Candidates[1]; + Assert.False(constraint2.Accept(context)); + } + + [Theory] + [InlineData("")] + [InlineData(null)] + public void EndpointConstraint_Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType) + { + // Arrange + var constraint1 = new ConsumesAttribute("application/json"); + var endpointWithConstraint = CreateEndpoint(constraint1); + + var constraint2 = new ConsumesAttribute("text/xml"); + var endpointWithConstraint2 = CreateEndpoint(constraint2); + + var endpointWithoutConstraint = CreateEndpoint(); + + var context = new EndpointConstraintContext(); + context.Candidates = new List() + { + new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), + new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), + }; + + context.HttpContext = CreateHttpContext(contentType: contentType); + + // Act & Assert + context.CurrentCandidate = context.Candidates[0]; + Assert.True(constraint1.Accept(context)); + context.CurrentCandidate = context.Candidates[1]; + Assert.True(constraint2.Accept(context)); + } + [Theory] [InlineData("application/xml")] [InlineData("application/custom")] @@ -404,11 +593,7 @@ namespace Microsoft.AspNetCore.Mvc private static RouteContext CreateRouteContext(string contentType = null, object routeValues = null) { - var httpContext = new DefaultHttpContext(); - if (contentType != null) - { - httpContext.Request.ContentType = contentType; - } + var httpContext = CreateHttpContext(contentType); var routeContext = new RouteContext(httpContext); routeContext.RouteData = new RouteData(); @@ -421,7 +606,22 @@ namespace Microsoft.AspNetCore.Mvc return routeContext; } - public interface ITestConsumeConstraint : IConsumesActionConstraint, IResourceFilter + private static HttpContext CreateHttpContext(string contentType = null, object routeValues = null) + { + var httpContext = new DefaultHttpContext(); + if (contentType != null) + { + httpContext.Request.ContentType = contentType; + } + + return httpContext; + } + + public interface ITestActionConsumeConstraint : IConsumesActionConstraint, IResourceFilter + { + } + + public interface ITestEndpointConsumeConstraint : IConsumesEndpointConstraint, IResourceFilter { } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs new file mode 100644 index 0000000000..ced707f674 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.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 ConsumesAttributeDispatchingTests : ConsumesAttributeTestsBase + { + public ConsumesAttributeDispatchingTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs index 181094b715..a8db21d806 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs @@ -1,169 +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.Net; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using BasicWebSite.Models; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Testing.xunit; -using Newtonsoft.Json; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class ConsumesAttributeTests : IClassFixture> + public class ConsumesAttributeTests : ConsumesAttributeTestsBase { public ConsumesAttributeTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Fact] - public async Task NoRequestContentType_SelectsActionWithoutConstraint() - { - // Arrange - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_Company/CreateProduct"); - - // Act - var response = await Client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("CreateProduct_Product_Text", body); - } - - [Fact] - public async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() - { - // Arrange - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_PassThrough/CreateProduct"); - - // Act - var response = await Client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("ConsumesAttribute_PassThrough_Product_Json", body); - } - - [Theory] - [InlineData("application/json")] - [InlineData("text/json")] - public async Task Selects_Action_BasedOnRequestContentType(string requestContentType) - { - // Arrange - var input = "{SampleString:\""+requestContentType+"\"}"; - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_AmbiguousActions/CreateProduct"); - request.Content = new StringContent(input, Encoding.UTF8, requestContentType); - - // Act - var response = await Client.SendAsync(request); - var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(requestContentType, product.SampleString); - } - - [Theory] - [InlineData("application/json")] - [InlineData("text/json")] - public async Task ActionLevelAttribute_OveridesClassLevel(string requestContentType) - { - // Arrange - var input = "{SampleString:\"" + requestContentType + "\"}"; - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_OverridesBase/CreateProduct"); - request.Content = new StringContent(input, Encoding.UTF8, requestContentType); - var expectedString = "ConsumesAttribute_OverridesBaseController_" + requestContentType; - - // Act - var response = await Client.SendAsync(request); - var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(expectedString, product.SampleString); - } - - [ConditionalFact] - // Mono issue - https://github.com/aspnet/External/issues/18 - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - public async Task DerivedClassLevelAttribute_OveridesBaseClassLevel() - { - // Arrange - var input = "" + - "application/xml"; - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_Overrides/CreateProduct"); - request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); - var expectedString = "ConsumesAttribute_OverridesController_application/xml"; - - // Act - var response = await Client.SendAsync(request); - var responseString = await response.Content.ReadAsStringAsync(); - var product = JsonConvert.DeserializeObject(responseString); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal(expectedString, product.SampleString); - } - - [Fact] - public async Task JsonSyntaxSuffix_SelectsActionConsumingJson() - { - // Arrange - var input = "{SampleString:\"some input\"}"; - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct"); - request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+json"); - - // Act - var response = await Client.SendAsync(request); - var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Read from JSON: some input", product.SampleString); - } - - [ConditionalFact] - // Mono issue - https://github.com/aspnet/External/issues/18 - [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - public async Task XmlSyntaxSuffix_SelectsActionConsumingXml() - { - // Arrange - var input = "" + - "some input"; - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct"); - request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+xml"); - - // Act - var response = await Client.SendAsync(request); - var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("Read from XML: some input", product.SampleString); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs new file mode 100644 index 0000000000..9a414488b1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -0,0 +1,175 @@ +// 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.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using BasicWebSite.Models; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Testing.xunit; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class ConsumesAttributeTestsBase : IClassFixture> where TStartup : class + { + protected ConsumesAttributeTestsBase(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; } + + [Fact] + public async Task NoRequestContentType_SelectsActionWithoutConstraint() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_Company/CreateProduct"); + + // Act + var response = await Client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("CreateProduct_Product_Text", body); + } + + [Fact] + public async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_PassThrough/CreateProduct"); + + // Act + var response = await Client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("ConsumesAttribute_PassThrough_Product_Json", body); + } + + [Theory] + [InlineData("application/json")] + [InlineData("text/json")] + public async Task Selects_Action_BasedOnRequestContentType(string requestContentType) + { + // Arrange + var input = "{SampleString:\""+requestContentType+"\"}"; + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_AmbiguousActions/CreateProduct"); + request.Content = new StringContent(input, Encoding.UTF8, requestContentType); + + // Act + var response = await Client.SendAsync(request); + var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(requestContentType, product.SampleString); + } + + [Theory] + [InlineData("application/json")] + [InlineData("text/json")] + public async Task ActionLevelAttribute_OveridesClassLevel(string requestContentType) + { + // Arrange + var input = "{SampleString:\"" + requestContentType + "\"}"; + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_OverridesBase/CreateProduct"); + request.Content = new StringContent(input, Encoding.UTF8, requestContentType); + var expectedString = "ConsumesAttribute_OverridesBaseController_" + requestContentType; + + // Act + var response = await Client.SendAsync(request); + var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedString, product.SampleString); + } + + [ConditionalFact] + // Mono issue - https://github.com/aspnet/External/issues/18 + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + public async Task DerivedClassLevelAttribute_OveridesBaseClassLevel() + { + // Arrange + var input = "" + + "application/xml"; + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_Overrides/CreateProduct"); + request.Content = new StringContent(input, Encoding.UTF8, "application/xml"); + var expectedString = "ConsumesAttribute_OverridesController_application/xml"; + + // Act + var response = await Client.SendAsync(request); + var responseString = await response.Content.ReadAsStringAsync(); + var product = JsonConvert.DeserializeObject(responseString); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedString, product.SampleString); + } + + [Fact] + public async Task JsonSyntaxSuffix_SelectsActionConsumingJson() + { + // Arrange + var input = "{SampleString:\"some input\"}"; + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct"); + request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+json"); + + // Act + var response = await Client.SendAsync(request); + var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Read from JSON: some input", product.SampleString); + } + + [ConditionalFact] + // Mono issue - https://github.com/aspnet/External/issues/18 + [FrameworkSkipCondition(RuntimeFrameworks.Mono)] + public async Task XmlSyntaxSuffix_SelectsActionConsumingXml() + { + // Arrange + var input = "" + + "some input"; + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_MediaTypeSuffix/CreateProduct"); + request.Content = new StringContent(input, Encoding.UTF8, "application/vnd.example+xml"); + + // Act + var response = await Client.SendAsync(request); + var product = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("Read from XML: some input", product.SampleString); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/StartupWithDispatching.cs b/test/WebSites/BasicWebSite/StartupWithDispatching.cs new file mode 100644 index 0000000000..4c5ed0dc1e --- /dev/null +++ b/test/WebSites/BasicWebSite/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 Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupWithDispatching + { + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + services.AddDispatcher(); + + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest) + .AddXmlDataContractSerializerFormatters(); + + services.ConfigureBaseWebSiteAuthPolicies(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDispatcher(); + + app.UseMvcWithEndpoint(routes => + { + routes.MapEndpoint( + "ActionAsMethod", + "{controller}/{action}", + defaults: new { controller = "Home", action = "Index" }); + }); + } + } +} \ No newline at end of file From 46189abda74fe5b5892a2bb5c660297701bc1410 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 28 Jun 2018 09:49:52 -0700 Subject: [PATCH 095/316] Refactoring for ApiConvention analyzers --- .../ApiControllerSymbolCache.cs | 57 ++ .../ApiControllerTypeCache.cs | 20 - .../ApiConventionAnalyzer.cs | 277 +++++++++ .../ApiResponseMetadata.cs | 23 + .../CodeAnalysisExtensions.cs | 68 ++- .../DiagnosticDescriptors.cs | 27 + .../SymbolApiConventionMatcher.cs | 197 ++++++ .../SymbolApiResponseMetadataProvider.cs | 71 ++- .../SymbolNames.cs | 22 +- .../ApiExplorer/ApiConventionMatcher.cs | 180 ++++++ .../ApiExplorer/ApiConventionResult.cs | 173 +----- .../ApiExplorer/ApiConventionMatcherTest.cs | 571 ++++++++++++++++++ .../ApiExplorer/ApiConventionResultTest.cs | 560 ----------------- .../ApiConventionAnalyzerIntegrationTest.cs | 166 +++++ .../ApiConventionAnalyzerTest.cs | 297 +++++++++ .../CodeAnalysisExtensionsTest.cs | 147 +++++ .../Mvc.Analyzers.Test.csproj | 1 + .../SymbolApiConventionMatcherTest.cs | 568 +++++++++++++++++ .../SymbolApiResponseMetadataProviderTest.cs | 41 +- ...tOfTReturningMethodWithoutAnyAttributes.cs | 21 + ...OfTReturningMethodWithoutSomeAttributes.cs | 25 + ...urned_ForControllerWithCustomConvention.cs | 50 ++ ...Attribute_ReturnsUndocumentedStatusCode.cs | 21 + ...Attribute_ReturnsUndocumentedStatusCode.cs | 21 + ...nouslyReturnsValue_WithoutDocumentation.cs | 23 + ...ributeReturnsValue_WithoutDocumentation.cs | 19 + ...fMethodWithAttribute_ReturnsDerivedType.cs | 23 + ...ntion_DoesNotReturnDocumentedStatusCode.cs | 20 + ...onvention_ReturnsUndocumentedStatusCode.cs | 25 + ...ibute_DoesNotReturnDocumentedStatusCode.cs | 21 + ...Attribute_ReturnsUndocumentedStatusCode.cs | 17 + ...ontroller_IfStatusCodesCannotBeInferred.cs | 12 + ...Controller_WithAllDocumentedStatusCodes.cs | 28 + ...gnosticsAreReturned_ForNonApiController.cs | 18 + ...agnosticsAreReturned_ForRazorPageModels.cs | 18 + ...reReturned_ForReturnStatementsInLambdas.cs | 41 ++ ...ned_ForReturnStatementsInLocalFunctions.cs | 34 ++ .../ApiConventionAnalyzerTestFile.cs | 56 ++ .../GetAttributes_BaseTypeWithAttributes.cs | 14 + .../GetAttributes_OnTypeWithAttributes.cs | 9 + .../GetAttributes_OnTypeWithoutAttributes.cs | 8 + .../SymbolApiConventionMatcherTestFile.cs | 55 ++ 42 files changed, 3223 insertions(+), 822 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs create mode 100644 test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs create mode 100644 test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs create mode 100644 test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs new file mode 100644 index 0000000000..5866ec36ba --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs @@ -0,0 +1,57 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal readonly struct ApiControllerSymbolCache + { + public ApiControllerSymbolCache(Compilation compilation) + { + ActionResultOfT = compilation.GetTypeByMetadataName(SymbolNames.ActionResultOfT); + ApiConventionNameMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionNameMatchAttribute); + ApiConventionTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeAttribute); + ApiConventionTypeMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeMatchAttribute); + ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute); + DefaultStatusCodeAttribute = compilation.GetTypeByMetadataName(SymbolNames.DefaultStatusCodeAttribute); + IActionResult = compilation.GetTypeByMetadataName(SymbolNames.IActionResult); + IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata); + IConvertToActionResult = compilation.GetTypeByMetadataName(SymbolNames.IConvertToActionResult); + NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); + NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); + ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); + + var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); + var members = disposable.GetMembers(nameof(IDisposable.Dispose)); + IDisposableDispose = members.Length == 1 ? (IMethodSymbol)members[0] : null; + } + + public INamedTypeSymbol ActionResultOfT { get; } + + public INamedTypeSymbol ApiConventionNameMatchAttribute { get; } + + public INamedTypeSymbol ApiConventionTypeAttribute { get; } + + public INamedTypeSymbol ApiConventionTypeMatchAttribute { get; } + + public INamedTypeSymbol ControllerAttribute { get; } + + public INamedTypeSymbol DefaultStatusCodeAttribute { get; } + + public INamedTypeSymbol IActionResult { get; } + + public INamedTypeSymbol IApiBehaviorMetadata { get; } + + public INamedTypeSymbol IConvertToActionResult { get; } + + public IMethodSymbol IDisposableDispose { get; } + + public INamedTypeSymbol NonActionAttribute { get; } + + public INamedTypeSymbol NonControllerAttribute { get; } + + public INamedTypeSymbol ProducesResponseTypeAttribute { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs deleted file mode 100644 index 8f4b2948e9..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerTypeCache.cs +++ /dev/null @@ -1,20 +0,0 @@ -// 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.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - internal readonly struct ApiControllerTypeCache - { - public ApiControllerTypeCache(Compilation compilation) - { - ApiConventionAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionAttribute); - ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); - } - - public INamedTypeSymbol ApiConventionAttribute { get; } - - public INamedTypeSymbol ProducesResponseTypeAttribute { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs new file mode 100644 index 0000000000..4efc083a19 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs @@ -0,0 +1,277 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ApiConventionAnalyzer : DiagnosticAnalyzer + { + private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, + DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, + DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(compilationStartAnalysisContext => + { + var symbolCache = new ApiControllerSymbolCache(compilationStartAnalysisContext.Compilation); + if (symbolCache.ApiConventionTypeAttribute == null || symbolCache.ApiConventionTypeAttribute.TypeKind == TypeKind.Error) + { + // No-op if we can't find types we care about. + return; + } + + InitializeWorker(compilationStartAnalysisContext, symbolCache); + }); + } + + private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, ApiControllerSymbolCache symbolCache) + { + compilationStartAnalysisContext.RegisterSyntaxNodeAction(syntaxNodeContext => + { + var methodSyntax = (MethodDeclarationSyntax)syntaxNodeContext.Node; + var semanticModel = syntaxNodeContext.SemanticModel; + var method = semanticModel.GetDeclaredSymbol(methodSyntax, syntaxNodeContext.CancellationToken); + + if (!ShouldEvaluateMethod(symbolCache, method)) + { + return; + } + + var conventionAttributes = GetConventionTypeAttributes(symbolCache, method); + var expectedResponseMetadata = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, conventionAttributes); + var actualResponseMetadata = new HashSet(); + + var context = new ApiConventionContext( + symbolCache, + syntaxNodeContext, + expectedResponseMetadata, + actualResponseMetadata); + + var hasUndocumentedStatusCodes = false; + foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) + { + hasUndocumentedStatusCodes |= VisitReturnStatementSyntax(context, returnStatementSyntax); + } + + if (hasUndocumentedStatusCodes) + { + // If we produced analyzer warnings about undocumented status codes, don't attempt to determine + // if there are documented status codes that are missing from the method body. + return; + } + + for (var i = 0; i < expectedResponseMetadata.Count; i++) + { + var expectedStatusCode = expectedResponseMetadata[i].StatusCode; + if (!actualResponseMetadata.Contains(expectedStatusCode)) + { + context.SyntaxNodeContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, + methodSyntax.Identifier.GetLocation(), + expectedStatusCode)); + } + } + + }, SyntaxKind.MethodDeclaration); + } + + internal IReadOnlyList GetConventionTypeAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + { + var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); + if (attributes.Length == 0) + { + attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); + } + + return attributes; + } + + // Returns true if the return statement returns an undocumented status code. + private static bool VisitReturnStatementSyntax( + in ApiConventionContext context, + ReturnStatementSyntax returnStatementSyntax) + { + var returnExpression = returnStatementSyntax.Expression; + if (returnExpression.IsMissing) + { + return false; + } + + var syntaxNodeContext = context.SyntaxNodeContext; + + var typeInfo = syntaxNodeContext.SemanticModel.GetTypeInfo(returnExpression, syntaxNodeContext.CancellationToken); + if (typeInfo.Type.TypeKind == TypeKind.Error) + { + return false; + } + + var location = returnStatementSyntax.GetLocation(); + var diagnostic = InspectReturnExpression(context, typeInfo.Type, location); + if (diagnostic != null) + { + context.SyntaxNodeContext.ReportDiagnostic(diagnostic); + return true; + } + + return false; + } + + internal static Diagnostic InspectReturnExpression(in ApiConventionContext context, ITypeSymbol type, Location location) + { + var defaultStatusCodeAttribute = type + .GetAttributes(context.SymbolCache.DefaultStatusCodeAttribute, inherit: true) + .FirstOrDefault(); + + if (defaultStatusCodeAttribute != null) + { + var statusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); + if (statusCode == null) + { + // Unable to read the status code. Treat this as valid. + return null; + } + + context.ActualResponseMetadata.Add(statusCode.Value); + if (!HasStatusCode(context.ExpectedResponseMetadata, statusCode.Value)) + { + return Diagnostic.Create( + DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, + location, + statusCode); + } + } + else if (!context.SymbolCache.IActionResult.IsAssignableFrom(type)) + { + if (!HasStatusCode(context.ExpectedResponseMetadata, 200) && !HasStatusCode(context.ExpectedResponseMetadata, 201)) + { + return Diagnostic.Create( + DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, + location); + } + } + + return null; + } + + internal static int? GetDefaultStatusCode(AttributeData attribute) + { + if (attribute != null && + attribute.ConstructorArguments.Length == 1 && + attribute.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attribute.ConstructorArguments[0].Value is int statusCode) + { + return statusCode; + } + + return null; + } + + internal static bool ShouldEvaluateMethod(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + { + if (method == null) + { + return false; + } + + if (method.ReturnsVoid || method.ReturnType.TypeKind == TypeKind.Error) + { + return false; + } + + if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute)) + { + return false; + } + + if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) + { + return false; + } + + if (!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose)) + { + return false; + } + + return true; + } + + internal static bool HasStatusCode(IList declaredApiResponseMetadata, int statusCode) + { + if (declaredApiResponseMetadata.Count == 0) + { + // When no status code is declared, a 200 OK is implied. + return statusCode == 200; + } + + for (var i = 0; i < declaredApiResponseMetadata.Count; i++) + { + if (declaredApiResponseMetadata[i].StatusCode == statusCode) + { + return true; + } + } + + return false; + } + + private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) + { + return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && + !syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression); + } + + + internal readonly struct ApiConventionContext + { + public ApiConventionContext( + ApiControllerSymbolCache symbolCache, + SyntaxNodeAnalysisContext syntaxNodeContext, + IList expectedResponseMetadata, + HashSet actualResponseMetadata, + Action reportDiagnostic = null) + { + SymbolCache = symbolCache; + SyntaxNodeContext = syntaxNodeContext; + ExpectedResponseMetadata = expectedResponseMetadata; + ActualResponseMetadata = actualResponseMetadata; + ReportDiagnosticAction = reportDiagnostic; + } + + public ApiControllerSymbolCache SymbolCache { get; } + public SyntaxNodeAnalysisContext SyntaxNodeContext { get; } + public IList ExpectedResponseMetadata { get; } + public HashSet ActualResponseMetadata { get; } + private Action ReportDiagnosticAction { get; } + + public void ReportDiagnostic(Diagnostic diagnostic) + { + if (ReportDiagnosticAction != null) + { + ReportDiagnosticAction(diagnostic); + } + + SyntaxNodeContext.ReportDiagnostic(diagnostic); + } + } + + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs new file mode 100644 index 0000000000..29ecd0ca27 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs @@ -0,0 +1,23 @@ +// 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.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal readonly struct ApiResponseMetadata + { + public ApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) + { + StatusCode = statusCode; + Attribute = attributeData; + Convention = convention; + } + + public int StatusCode { get; } + + public AttributeData Attribute { get; } + + public IMethodSymbol Convention { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs index 3fca8e3851..4211409bf4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -11,29 +11,22 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers internal static class CodeAnalysisExtensions { public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) - { - Debug.Assert(typeSymbol != null); - Debug.Assert(attribute != null); - - if (!inherit) - { - return HasAttribute(typeSymbol, attribute); - } - - foreach (var type in typeSymbol.GetTypeHierarchy()) - { - if (type.HasAttribute(attribute)) - { - return true; - } - } - - return false; - } + => GetAttributes(typeSymbol, attribute, inherit).Any(); public static bool HasAttribute(this IMethodSymbol methodSymbol, ITypeSymbol attribute, bool inherit) => GetAttributes(methodSymbol, attribute, inherit).Any(); + public static IEnumerable GetAttributes(this ISymbol symbol, ITypeSymbol attribute) + { + foreach (var declaredAttribute in symbol.GetAttributes()) + { + if (attribute.IsAssignableFrom(declaredAttribute.AttributeClass)) + { + yield return declaredAttribute; + } + } + } + public static IEnumerable GetAttributes(this IMethodSymbol methodSymbol, ITypeSymbol attribute, bool inherit) { Debug.Assert(methodSymbol != null); @@ -55,6 +48,25 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } } + public static IEnumerable GetAttributes(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) + { + Debug.Assert(typeSymbol != null); + Debug.Assert(attribute != null); + + foreach (var type in GetTypeHierarchy(typeSymbol)) + { + foreach (var attributeData in GetAttributes(type, attribute)) + { + yield return attributeData; + } + + if (!inherit) + { + break; + } + } + } + public static bool HasAttribute(this IPropertySymbol propertySymbol, ITypeSymbol attribute, bool inherit) { Debug.Assert(propertySymbol != null); @@ -78,11 +90,16 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } - public static bool IsAssignableFrom(this ITypeSymbol source, INamedTypeSymbol target) + public static bool IsAssignableFrom(this ITypeSymbol source, ITypeSymbol target) { Debug.Assert(source != null); Debug.Assert(target != null); + if (source == target) + { + return true; + } + if (source.TypeKind == TypeKind.Interface) { foreach (var @interface in target.AllInterfaces) @@ -120,17 +137,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } - private static IEnumerable GetAttributes(this ISymbol symbol, ITypeSymbol attribute) - { - foreach (var declaredAttribute in symbol.GetAttributes()) - { - if (attribute.IsAssignableFrom(declaredAttribute.AttributeClass)) - { - yield return declaredAttribute; - } - } - } - private static IEnumerable GetTypeHierarchy(this ITypeSymbol typeSymbol) { while (typeSymbol != null) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs index b2684297bd..ea5e213385 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs @@ -42,5 +42,32 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1004_ActionReturnsUndocumentedStatusCode = + new DiagnosticDescriptor( + "MVC1004", + "Action returns undeclared status code.", + "Action method returns undeclared status code '{0}'.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1005_ActionReturnsUndocumentedSuccessResult = + new DiagnosticDescriptor( + "MVC1005", + "Action returns undeclared success result.", + "Action method returns a success result without a corresponding ProducesResponseType.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1006_ActionDoesNotReturnDocumentedStatusCode = + new DiagnosticDescriptor( + "MVC1006", + "Action documents status code that is not returned.", + "Action method documents status code '{0}' without a corresponding return type.", + "Usage", + DiagnosticSeverity.Info, + isEnabledByDefault: false); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs new file mode 100644 index 0000000000..878d0189e9 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs @@ -0,0 +1,197 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal static class SymbolApiConventionMatcher + { + internal static bool IsMatch(ApiControllerSymbolCache symbolCache, IMethodSymbol method, IMethodSymbol conventionMethod) + { + return MethodMatches() && ParametersMatch(); + + bool MethodMatches() + { + var methodNameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionMethod); + if (!IsNameMatch(method.Name, conventionMethod.Name, methodNameMatchBehavior)) + { + return false; + } + + return true; + } + + bool ParametersMatch() + { + var methodParameters = method.Parameters; + var conventionMethodParameters = conventionMethod.Parameters; + + for (var i = 0; i < conventionMethodParameters.Length; i++) + { + var conventionParameter = conventionMethodParameters[i]; + if (conventionParameter.IsParams) + { + return true; + } + + if (methodParameters.Length <= i) + { + return false; + } + + var nameMatchBehavior = GetNameMatchBehavior(symbolCache, conventionParameter); + var typeMatchBehavior = GetTypeMatchBehavior(symbolCache, conventionParameter); + + if (!IsTypeMatch(methodParameters[i].Type, conventionParameter.Type, typeMatchBehavior) || + !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior)) + { + return false; + } + } + + // Ensure convention has at least as many parameters as the method. params convention argument are handled + // inside the for loop. + return methodParameters.Length == conventionMethodParameters.Length; + } + } + + internal static SymbolApiConventionNameMatchBehavior GetNameMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol) + { + var attribute = symbol.GetAttributes(symbolCache.ApiConventionNameMatchAttribute).FirstOrDefault(); + if (attribute == null || + attribute.ConstructorArguments.Length != 1 || + attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum) + { + return SymbolApiConventionNameMatchBehavior.Exact; + } + + var intValue = (int)attribute.ConstructorArguments[0].Value; + return (SymbolApiConventionNameMatchBehavior)intValue; + } + + internal static SymbolApiConventionTypeMatchBehavior GetTypeMatchBehavior(ApiControllerSymbolCache symbolCache, ISymbol symbol) + { + var attribute = symbol.GetAttributes(symbolCache.ApiConventionTypeMatchAttribute).FirstOrDefault(); + if (attribute == null || + attribute.ConstructorArguments.Length != 1 || + attribute.ConstructorArguments[0].Kind != TypedConstantKind.Enum) + { + return SymbolApiConventionTypeMatchBehavior.AssignableFrom; + } + + var intValue = (int)attribute.ConstructorArguments[0].Value; + return (SymbolApiConventionTypeMatchBehavior)intValue; + } + + internal static bool IsNameMatch(string name, string conventionName, SymbolApiConventionNameMatchBehavior nameMatchBehavior) + { + switch (nameMatchBehavior) + { + case SymbolApiConventionNameMatchBehavior.Any: + return true; + + case SymbolApiConventionNameMatchBehavior.Exact: + return string.Equals(name, conventionName, StringComparison.Ordinal); + + case SymbolApiConventionNameMatchBehavior.Prefix: + return IsNameMatchPrefix(); + + case SymbolApiConventionNameMatchBehavior.Suffix: + return IsNameMatchSuffix(); + + default: + return false; + } + + bool IsNameMatchPrefix() + { + if (name.Length < conventionName.Length) + { + return false; + } + + if (name.Length == conventionName.Length) + { + // name = "Post", conventionName = "Post" + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + if (!name.StartsWith(conventionName, StringComparison.Ordinal)) + { + // name = "GetPerson", conventionName = "Post" + return false; + } + + // Check for name = "PostPerson", conventionName = "Post" + // Verify the first letter after the convention name is upper case. In this case 'P' from "Person" + return char.IsUpper(name[conventionName.Length]); + } + + bool IsNameMatchSuffix() + { + if (name.Length < conventionName.Length) + { + // name = "person", conventionName = "personName" + return false; + } + + if (name.Length == conventionName.Length) + { + // name = id, conventionName = id + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + // Check for name = personName, conventionName = name + var index = name.Length - conventionName.Length - 1; + if (!char.IsLower(name[index])) + { + // Verify letter before "name" is lowercase. In this case the letter 'n' at the end of "person" + return false; + } + + index++; + if (name[index] != char.ToUpper(conventionName[0])) + { + // Verify the first letter from convention is upper case. In this case 'n' from "name" + return false; + } + + // Match the remaining letters with exact case. i.e. match "ame" from "personName", "name" + index++; + return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; + } + } + + internal static bool IsTypeMatch(ITypeSymbol type, ITypeSymbol conventionType, SymbolApiConventionTypeMatchBehavior typeMatchBehavior) + { + switch (typeMatchBehavior) + { + case SymbolApiConventionTypeMatchBehavior.Any: + return true; + + case SymbolApiConventionTypeMatchBehavior.AssignableFrom: + return conventionType.IsAssignableFrom(type); + + default: + return false; + } + } + + internal enum SymbolApiConventionTypeMatchBehavior + { + Any, + AssignableFrom + } + + internal enum SymbolApiConventionNameMatchBehavior + { + Any, + Exact, + Prefix, + Suffix, + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs index bfc6935572..c5c1367d43 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Analyzers @@ -12,10 +13,58 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private const string StatusCodeProperty = "StatusCode"; private const string StatusCodeConstructorParameter = "statusCode"; - internal static IList GetResponseMetadata(ApiControllerTypeCache typeCache, IMethodSymbol methodSymbol) + internal static IList GetResponseMetadata( + ApiControllerSymbolCache symbolCache, + IMethodSymbol method, + IReadOnlyList conventionTypeAttributes) + { + var metadataItems = GetResponseMetadataFromMethodAttributes(symbolCache, method); + if (metadataItems.Count != 0) + { + return metadataItems; + } + + metadataItems = GetResponseMetadataFromConventions(symbolCache, method, conventionTypeAttributes); + return metadataItems; + } + + private static IList GetResponseMetadataFromConventions( + ApiControllerSymbolCache symbolCache, + IMethodSymbol method, + IReadOnlyList attributes) + { + foreach (var attribute in attributes) + { + if (attribute.ConstructorArguments.Length != 1 || + attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type || + !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType)) + { + continue; + } + + foreach (var conventionMethod in conventionType.GetMembers().OfType()) + { + if (!conventionMethod.IsStatic || conventionMethod.DeclaredAccessibility != Accessibility.Public) + { + continue; + } + + if (!SymbolApiConventionMatcher.IsMatch(symbolCache, method, conventionMethod)) + { + continue; + } + + return GetResponseMetadataFromMethodAttributes(symbolCache, conventionMethod); + } + } + + return Array.Empty(); + } + + private static IList GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) { - var responseMetadataAttributes = methodSymbol.GetAttributes(typeCache.ProducesResponseTypeAttribute, inherit: true); var metadataItems = new List(); + var responseMetadataAttributes = methodSymbol.GetAttributes(symbolCache.ProducesResponseTypeAttribute, inherit: true); foreach (var attribute in responseMetadataAttributes) { var statusCode = GetStatusCode(attribute); @@ -34,7 +83,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { var namedArgument = attribute.NamedArguments[i]; var namedArgumentValue = namedArgument.Value; - if (string.Equals(namedArgument.Key, StatusCodeProperty, StringComparison.Ordinal) && + if (string.Equals(namedArgument.Key, StatusCodeProperty, StringComparison.Ordinal) && namedArgumentValue.Kind == TypedConstantKind.Primitive && (namedArgumentValue.Type.SpecialType & SpecialType.System_Int32) == SpecialType.System_Int32 && namedArgumentValue.Value is int statusCode) @@ -71,20 +120,4 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return DefaultStatusCode; } } - - internal readonly struct ApiResponseMetadata - { - public ApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) - { - StatusCode = statusCode; - Attribute = attributeData; - Convention = convention; - } - - public int StatusCode { get; } - - public AttributeData Attribute { get; } - - public IMethodSymbol Convention { get; } - } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index 98cd8666be..d57a911f2b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -7,10 +7,26 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; - public const string ApiConventionAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionAttribute"; + public const string ApiConventionNameMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchAttribute"; + + public const string ApiConventionTypeMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionTypeMatchAttribute"; + + public const string ApiConventionTypeAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute"; + + public const string ActionResultOfT = "Microsoft.AspNetCore.Mvc.ActionResult`1"; public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; + public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; + + public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; + + public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; + + public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; + + public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult"; + public const string IFilterMetadataType = "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata"; public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; @@ -19,6 +35,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; + public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; + + public const string NonControllerAttribute = "Microsoft.AspNetCore.Mvc.NonControllerAttribute"; + public const string PageModelAttributeType = "Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageModelAttribute"; public const string PartialMethod = "Partial"; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs new file mode 100644 index 0000000000..b9e6f593c3 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs @@ -0,0 +1,180 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + internal static class ApiConventionMatcher + { + internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod) + { + return MethodMatches() && ParametersMatch(); + + bool MethodMatches() + { + var methodNameMatchBehavior = GetNameMatchBehavior(conventionMethod); + if (!IsNameMatch(methodInfo.Name, conventionMethod.Name, methodNameMatchBehavior)) + { + return false; + } + + return true; + } + + bool ParametersMatch() + { + var methodParameters = methodInfo.GetParameters(); + var conventionMethodParameters = conventionMethod.GetParameters(); + + for (var i = 0; i < conventionMethodParameters.Length; i++) + { + var conventionParameter = conventionMethodParameters[i]; + if (conventionParameter.IsDefined(typeof(ParamArrayAttribute))) + { + return true; + } + + if (methodParameters.Length <= i) + { + return false; + } + + var nameMatchBehavior = GetNameMatchBehavior(conventionParameter); + var typeMatchBehavior = GetTypeMatchBehavior(conventionParameter); + + if (!IsTypeMatch(methodParameters[i].ParameterType, conventionParameter.ParameterType, typeMatchBehavior) || + !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior)) + { + return false; + } + } + + // Ensure convention has at least as many parameters as the method. params convention argument are handled + // inside the for loop. + return methodParameters.Length == conventionMethodParameters.Length; + } + } + + internal static ApiConventionNameMatchBehavior GetNameMatchBehavior(ICustomAttributeProvider attributeProvider) + { + var attribute = GetCustomAttribute(attributeProvider); + return attribute?.MatchBehavior ?? ApiConventionNameMatchBehavior.Exact; + } + + internal static ApiConventionTypeMatchBehavior GetTypeMatchBehavior(ICustomAttributeProvider attributeProvider) + { + var attribute = GetCustomAttribute(attributeProvider); + return attribute?.MatchBehavior ?? ApiConventionTypeMatchBehavior.AssignableFrom; + } + + private static TAttribute GetCustomAttribute(ICustomAttributeProvider attributeProvider) + { + var attributes = attributeProvider.GetCustomAttributes(inherit: false); + for (var i = 0; i < attributes.Length; i++) + { + if (attributes[i] is TAttribute attribute) + { + return attribute; + } + } + + return default; + } + + internal static bool IsNameMatch(string name, string conventionName, ApiConventionNameMatchBehavior nameMatchBehavior) + { + switch (nameMatchBehavior) + { + case ApiConventionNameMatchBehavior.Any: + return true; + + case ApiConventionNameMatchBehavior.Exact: + return string.Equals(name, conventionName, StringComparison.Ordinal); + + case ApiConventionNameMatchBehavior.Prefix: + return IsNameMatchPrefix(); + + case ApiConventionNameMatchBehavior.Suffix: + return IsNameMatchSuffix(); + + default: + return false; + } + + bool IsNameMatchPrefix() + { + if (name.Length < conventionName.Length) + { + return false; + } + + if (name.Length == conventionName.Length) + { + // name = "Post", conventionName = "Post" + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + if (!name.StartsWith(conventionName, StringComparison.Ordinal)) + { + // name = "GetPerson", conventionName = "Post" + return false; + } + + // Check for name = "PostPerson", conventionName = "Post" + // Verify the first letter after the convention name is upper case. In this case 'P' from "Person" + return char.IsUpper(name[conventionName.Length]); + } + + bool IsNameMatchSuffix() + { + if (name.Length < conventionName.Length) + { + // name = "person", conventionName = "personName" + return false; + } + + if (name.Length == conventionName.Length) + { + // name = id, conventionName = id + return string.Equals(name, conventionName, StringComparison.Ordinal); + } + + // Check for name = personName, conventionName = name + var index = name.Length - conventionName.Length - 1; + if (!char.IsLower(name[index])) + { + // Verify letter before "name" is lowercase. In this case the letter 'n' at the end of "person" + return false; + } + + index++; + if (name[index] != char.ToUpper(conventionName[0])) + { + // Verify the first letter from convention is upper case. In this case 'n' from "name" + return false; + } + + // Match the remaining letters with exact case. i.e. match "ame" from "personName", "name" + index++; + return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; + } + } + + internal static bool IsTypeMatch(Type type, Type conventionType, ApiConventionTypeMatchBehavior typeMatchBehavior) + { + switch (typeMatchBehavior) + { + case ApiConventionTypeMatchBehavior.Any: + return true; + + case ApiConventionTypeMatchBehavior.AssignableFrom: + return conventionType.IsAssignableFrom(type); + + default: + return false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs index ca4d85d177..28890d7e13 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs @@ -46,9 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer private static MethodInfo GetConventionMethod(MethodInfo method, Type conventionType) { - foreach (var conventionMethod in conventionType.GetMethods()) + foreach (var conventionMethod in conventionType.GetMethods(BindingFlags.Public | BindingFlags.Static)) { - if (IsMatch(method, conventionMethod)) + if (ApiConventionMatcher.IsMatch(method, conventionMethod)) { return conventionMethod; } @@ -56,174 +56,5 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return null; } - - internal static bool IsMatch(MethodInfo methodInfo, MethodInfo conventionMethod) - { - return MethodMatches() && ParametersMatch(); - - bool MethodMatches() - { - var methodNameMatchBehavior = GetNameMatchBehavior(conventionMethod); - if (!IsNameMatch(methodInfo.Name, conventionMethod.Name, methodNameMatchBehavior)) - { - return false; - } - - return true; - } - - bool ParametersMatch() - { - var methodParameters = methodInfo.GetParameters(); - var conventionMethodParameters = conventionMethod.GetParameters(); - - for (var i = 0; i < conventionMethodParameters.Length; i++) - { - var conventionParameter = conventionMethodParameters[i]; - if (conventionParameter.IsDefined(typeof(ParamArrayAttribute))) - { - return true; - } - - if (methodParameters.Length <= i) - { - return false; - } - - var nameMatchBehavior = GetNameMatchBehavior(conventionParameter); - var typeMatchBehavior = GetTypeMatchBehavior(conventionParameter); - - if (!IsTypeMatch(methodParameters[i].ParameterType, conventionParameter.ParameterType, typeMatchBehavior) || - !IsNameMatch(methodParameters[i].Name, conventionParameter.Name, nameMatchBehavior)) - { - return false; - } - } - - // Ensure convention has at least as many parameters as the method. params convention argument are handled - // inside the for loop. - return methodParameters.Length == conventionMethodParameters.Length; - } - } - - internal static ApiConventionNameMatchBehavior GetNameMatchBehavior(ICustomAttributeProvider attributeProvider) - { - var attribute = GetCustomAttribute(attributeProvider); - return attribute?.MatchBehavior ?? ApiConventionNameMatchBehavior.Exact; - } - - internal static ApiConventionTypeMatchBehavior GetTypeMatchBehavior(ICustomAttributeProvider attributeProvider) - { - var attribute = GetCustomAttribute(attributeProvider); - return attribute?.MatchBehavior ?? ApiConventionTypeMatchBehavior.AssignableFrom; - } - - private static TAttribute GetCustomAttribute(ICustomAttributeProvider attributeProvider) - { - var attributes = attributeProvider.GetCustomAttributes(inherit: false); - for (var i = 0; i < attributes.Length; i++) - { - if (attributes[i] is TAttribute attribute) - { - return attribute; - } - } - - return default; - } - - internal static bool IsNameMatch(string name, string conventionName, ApiConventionNameMatchBehavior nameMatchBehavior) - { - switch (nameMatchBehavior) - { - case ApiConventionNameMatchBehavior.Any: - return true; - - case ApiConventionNameMatchBehavior.Exact: - return string.Equals(name, conventionName, StringComparison.Ordinal); - - case ApiConventionNameMatchBehavior.Prefix: - return IsNameMatchPrefix(); - - case ApiConventionNameMatchBehavior.Suffix: - return IsNameMatchSuffix(); - - default: - return false; - } - - bool IsNameMatchPrefix() - { - if (name.Length < conventionName.Length) - { - return false; - } - - if (name.Length == conventionName.Length) - { - // name = "Post", conventionName = "Post" - return string.Equals(name, conventionName, StringComparison.Ordinal); - } - - if (!name.StartsWith(conventionName, StringComparison.Ordinal)) - { - // name = "GetPerson", conventionName = "Post" - return false; - } - - // Check for name = "PostPerson", conventionName = "Post" - // Verify the first letter after the convention name is upper case. In this case 'P' from "Person" - return char.IsUpper(name[conventionName.Length]); - } - - bool IsNameMatchSuffix() - { - if (name.Length < conventionName.Length) - { - // name = "person", conventionName = "personName" - return false; - } - - if (name.Length == conventionName.Length) - { - // name = id, conventionName = id - return string.Equals(name, conventionName, StringComparison.Ordinal); - } - - // Check for name = personName, conventionName = name - var index = name.Length - conventionName.Length - 1; - if (!char.IsLower(name[index])) - { - // Verify letter before "name" is lowercase. In this case the letter 'n' at the end of "person" - return false; - } - - index++; - if (name[index] != char.ToUpper(conventionName[0])) - { - // Verify the first letter from convention is upper case. In this case 'n' from "name" - return false; - } - - // Match the remaining letters with exact case. i.e. match "ame" from "personName", "name" - index++; - return string.Compare(name, index, conventionName, 1, conventionName.Length - 1, StringComparison.Ordinal) == 0; - } - } - - internal static bool IsTypeMatch(Type type, Type conventionType, ApiConventionTypeMatchBehavior typeMatchBehavior) - { - switch (typeMatchBehavior) - { - case ApiConventionTypeMatchBehavior.Any: - return true; - - case ApiConventionTypeMatchBehavior.AssignableFrom: - return conventionType.IsAssignableFrom(type); - - default: - return false; - } - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs new file mode 100644 index 0000000000..72526bbd2b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs @@ -0,0 +1,571 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApiExplorer +{ + public class ApiConventionMatcherTest + { + [Theory] + [InlineData("Method", "method")] + [InlineData("Method", "ConventionMethod")] + [InlineData("p", "model")] + [InlineData("person", "model")] + public void IsNameMatch_WithAny_AlwaysReturnsTrue(string name, string conventionName) + { + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "Name"; + var conventionName = "name"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "Name"; + var conventionName = "Different"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSubString() + { + // Arrange + var name = "RegularName"; + var conventionName = "Regular"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSuperString() + { + // Arrange + var name = "Regular"; + var conventionName = "RegularName"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsTrue_IfExactMatch() + { + // Arrange + var name = "parameterName"; + var conventionName = "parameterName"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNamesAreExact() + { + // Arrange + var name = "PostPerson"; + var conventionName = "PostPerson"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNameIsProperPrefix() + { + // Arrange + var name = "PostPerson"; + var conventionName = "Post"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "GetPerson"; + var conventionName = "Post"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "GetPerson"; + var conventionName = "post"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() + { + // Arrange + var name = "Postman"; + var conventionName = "Post"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsSuffix() + { + // Arrange + var name = "GoPost"; + var conventionName = "Post"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "name"; + var conventionName = "diff"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNameIsNotSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "idx"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsExact() + { + // Arrange + var name = "test"; + var conventionName = "test"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameDiffersInCase() + { + // Arrange + var name = "test"; + var conventionName = "Test"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsProperSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "id"; + + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("candid", "id")] + [InlineData("canDid", "id")] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameIsNotProperSuffix(string name, string conventionName) + { + // Act + var result = ApiConventionMatcher.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(typeof(object), typeof(object))] + [InlineData(typeof(int), typeof(void))] + [InlineData(typeof(string), typeof(DateTime))] + public void IsTypeMatch_WithAny_ReturnsTrue(Type type, Type conventionType) + { + // Act + var result = ApiConventionMatcher.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssignableFrom_ReturnsTrueForExact() + { + // Arrange + var type = typeof(Base); + var conventionType = typeof(Base); + + // Act + var result = ApiConventionMatcher.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssignableFrom_ReturnsTrueForDerived() + { + // Arrange + var type = typeof(Derived); + var conventionType = typeof(Base); + + // Act + var result = ApiConventionMatcher.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsTypeMatch_WithAssignableFrom_ReturnsFalseForBaseTypes() + { + // Arrange + var type = typeof(Base); + var conventionType = typeof(Derived); + + // Act + var result = ApiConventionMatcher.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsTypeMatch_WithAssignableFrom_ReturnsFalseForUnrelated() + { + // Arrange + var type = typeof(string); + var conventionType = typeof(Derived); + + // Act + var result = ApiConventionMatcher.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfMethodNamesDoNotMatch() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Post)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IMethodHasMoreParametersThanConvention() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetNoArgs)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfMethodHasFewerParametersThanConvention() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetTwoArgs)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsFalse_IfParametersDoNotMatch() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetParameterNotMatching)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Get)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Get)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMatch_ReturnsTrue_IfParamsArrayMatchesRemainingArguments() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.Search)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Search)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsMatch_WithEmpty_MatchesMethodWithNoParameters() + { + // Arrange + var method = typeof(TestController).GetMethod(nameof(TestController.SearchEmpty)); + var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.SearchWithParams)); + + // Act + var result = ApiConventionMatcher.IsMatch(method, conventionMethod); + + // Assert + Assert.True(result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsExact_WhenNoAttributesArePresent() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Exact; + var attributes = new object[0]; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsExact_WhenNoNameMatchBehaviorAttributeIsSpecified() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Exact; + var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetNameMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = ApiConventionNameMatchBehavior.Prefix; + var attributes = new object[] + { + new CLSCompliantAttribute(false), + new ApiConventionNameMatchAttribute(expected), + new ProducesResponseTypeAttribute(200) } + ; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetNameMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoAttributesArePresent() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.AssignableFrom; + var attributes = new object[0]; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoMatchingAttributesArePresent() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.AssignableFrom; + var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public void GetTypeMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = ApiConventionTypeMatchBehavior.Any; + var attributes = new object[] + { + new CLSCompliantAttribute(false), + new ApiConventionTypeMatchAttribute(expected), + new ProducesResponseTypeAttribute(200) } + ; + var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); + + // Act + var result = ApiConventionMatcher.GetTypeMatchBehavior(provider); + + // Assert + Assert.Equal(expected, result); + } + + public class Base { } + + public class Derived : Base { } + + public class TestController + { + public IActionResult Get(int id) => null; + + public IActionResult Search(string searchTerm, bool sortDescending, int page) => null; + + public IActionResult SearchEmpty() => null; + } + + public static class TestConvention + { + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Get(int id) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetNoArgs() { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetTwoArgs(int id, string name) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Post(Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void GetParameterNotMatching([ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.AssignableFrom)] Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void Search( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Exact)] + string searchTerm, + params object[] others) + { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void SearchWithParams(params object[] others) { } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs index e1290a0421..851e607880 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs @@ -3,8 +3,6 @@ using System; using System.Linq; -using System.Reflection; -using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.ApiExplorer @@ -196,563 +194,5 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } public class User { } - - [Theory] - [InlineData("Method", "method")] - [InlineData("Method", "ConventionMethod")] - [InlineData("p", "model")] - [InlineData("person", "model")] - public void IsNameMatch_WithAny_AlwaysReturnsTrue(string name, string conventionName) - { - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Any); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNameMatch_WithExact_ReturnsFalse_IfNamesDifferInCase() - { - // Arrange - var name = "Name"; - var conventionName = "name"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithExact_ReturnsFalse_IfNamesAreDifferent() - { - // Arrange - var name = "Name"; - var conventionName = "Different"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSubString() - { - // Arrange - var name = "RegularName"; - var conventionName = "Regular"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSuperString() - { - // Arrange - var name = "Regular"; - var conventionName = "RegularName"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithExact_ReturnsTrue_IfExactMatch() - { - // Arrange - var name = "parameterName"; - var conventionName = "parameterName"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Exact); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsTrue_IfNamesAreExact() - { - // Arrange - var name = "PostPerson"; - var conventionName = "PostPerson"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsTrue_IfNameIsProperPrefix() - { - // Arrange - var name = "PostPerson"; - var conventionName = "Post"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesAreDifferent() - { - // Arrange - var name = "GetPerson"; - var conventionName = "Post"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesDifferInCase() - { - // Arrange - var name = "GetPerson"; - var conventionName = "post"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() - { - // Arrange - var name = "Postman"; - var conventionName = "Post"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsSuffix() - { - // Arrange - var name = "GoPost"; - var conventionName = "Post"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Prefix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithSuffix_ReturnsFalse_IfNamesAreDifferent() - { - // Arrange - var name = "name"; - var conventionName = "diff"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithSuffix_ReturnsFalse_IfNameIsNotSuffix() - { - // Arrange - var name = "personId"; - var conventionName = "idx"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsExact() - { - // Arrange - var name = "test"; - var conventionName = "test"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNameMatch_WithSuffix_ReturnFalse_IfNameDiffersInCase() - { - // Arrange - var name = "test"; - var conventionName = "Test"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsProperSuffix() - { - // Arrange - var name = "personId"; - var conventionName = "id"; - - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.True(result); - } - - [Theory] - [InlineData("candid", "id")] - [InlineData("canDid", "id")] - public void IsNameMatch_WithSuffix_ReturnFalse_IfNameIsNotProperSuffix(string name, string conventionName) - { - // Act - var result = ApiConventionResult.IsNameMatch(name, conventionName, ApiConventionNameMatchBehavior.Suffix); - - // Assert - Assert.False(result); - } - - [Theory] - [InlineData(typeof(object), typeof(object))] - [InlineData(typeof(int), typeof(void))] - [InlineData(typeof(string), typeof(DateTime))] - public void IsTypeMatch_WithAny_ReturnsTrue(Type type, Type conventionType) - { - // Act - var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.Any); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsTypeMatch_WithAssinableFrom_ReturnsTrueForExact() - { - // Arrange - var type = typeof(Base); - var conventionType = typeof(Base); - - // Act - var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsTypeMatch_WithAssinableFrom_ReturnsTrueForDerived() - { - // Arrange - var type = typeof(Derived); - var conventionType = typeof(Base); - - // Act - var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsTypeMatch_WithAssinableFrom_ReturnsFalseForBaseTypes() - { - // Arrange - var type = typeof(Base); - var conventionType = typeof(Derived); - - // Act - var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsTypeMatch_WithAssinableFrom_ReturnsFalseForUnrelated() - { - // Arrange - var type = typeof(string); - var conventionType = typeof(Derived); - - // Act - var result = ApiConventionResult.IsTypeMatch(type, conventionType, ApiConventionTypeMatchBehavior.AssignableFrom); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IfMethodNamesDoNotMatch() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Get)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Post)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IMethodHasMoreParametersThanConvention() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Get)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetNoArgs)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IfMethodHasFewerParametersThanConvention() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Get)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetTwoArgs)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsFalse_IfParametersDoNotMatch() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Get)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.GetParameterNotMatching)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.False(result); - } - - [Fact] - public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Get)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Get)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsMatch_ReturnsTrue_IfParamsArrayMatchesRemainingArguments() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.Search)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.Search)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsMatch_WithEmpty_MatchesMethodWithNoParameters() - { - // Arrange - var method = typeof(TestController).GetMethod(nameof(TestController.SearchEmpty)); - var conventionMethod = typeof(TestConvention).GetMethod(nameof(TestConvention.SearchWithParams)); - - // Act - var result = ApiConventionResult.IsMatch(method, conventionMethod); - - // Assert - Assert.True(result); - } - - [Fact] - public void GetNameMatchBehavior_ReturnsExact_WhenNoAttributesArePresent() - { - // Arrange - var expected = ApiConventionNameMatchBehavior.Exact; - var attributes = new object[0]; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetNameMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void GetNameMatchBehavior_ReturnsExact_WhenNoNameMatchBehaviorAttributeIsSpecified() - { - // Arrange - var expected = ApiConventionNameMatchBehavior.Exact; - var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetNameMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void GetNameMatchBehavior_ReturnsValueFromAttributes() - { - // Arrange - var expected = ApiConventionNameMatchBehavior.Prefix; - var attributes = new object[] - { - new CLSCompliantAttribute(false), - new ApiConventionNameMatchAttribute(expected), - new ProducesResponseTypeAttribute(200) } - ; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetNameMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoAttributesArePresent() - { - // Arrange - var expected = ApiConventionTypeMatchBehavior.AssignableFrom; - var attributes = new object[0]; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetTypeMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoMatchingAttributesArePresent() - { - // Arrange - var expected = ApiConventionTypeMatchBehavior.AssignableFrom; - var attributes = new object[] { new CLSCompliantAttribute(false), new ProducesResponseTypeAttribute(200) }; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetTypeMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - [Fact] - public void GetTypeMatchBehavior_ReturnsValueFromAttributes() - { - // Arrange - var expected = ApiConventionTypeMatchBehavior.Any; - var attributes = new object[] - { - new CLSCompliantAttribute(false), - new ApiConventionTypeMatchAttribute(expected), - new ProducesResponseTypeAttribute(200) } - ; - var provider = Mock.Of(p => p.GetCustomAttributes(false) == attributes); - - // Act - var result = ApiConventionResult.GetTypeMatchBehavior(provider); - - // Assert - Assert.Equal(expected, result); - } - - public class Base { } - - public class Derived : Base { } - - public class TestController - { - public IActionResult Get(int id) => null; - - public IActionResult Search(string searchTerm, bool sortDescending, int page) => null; - - public IActionResult SearchEmpty() => null; - } - - public static class TestConvention - { - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] - public static void Get(int id) { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] - public static void GetNoArgs() { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] - public static void GetTwoArgs(int id, string name) { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] - public static void Post(Derived model) { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] - public static void GetParameterNotMatching([ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.AssignableFrom)] Derived model) { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] - public static void Search( - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Exact)] - string searchTerm, - params object[] others) - { } - - [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] - public static void SearchWithParams(params object[] others) { } - } } } diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs new file mode 100644 index 0000000000..75307db01f --- /dev/null +++ b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -0,0 +1,166 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class ApiConventionAnalyzerIntegrationTest + { + private MvcDiagnosticAnalyzerRunner Executor { get; } = new MvcDiagnosticAnalyzerRunner(new ApiConventionAnalyzer()); + + [Fact] + public Task NoDiagnosticsAreReturned_ForNonApiController() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForRazorPageModels() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForReturnStatementsInLambdas() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + + [Fact] + public Task DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + + [Fact] + public Task DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 200); + + [Fact] + public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + + [Fact] + public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 422); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 400); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation() + => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation() + => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType() + => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode() + => RunTestFor1006(400); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode() + => RunTestFor1006(404); + + private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Executor.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Empty(result); + } + + private Task RunTest(DiagnosticDescriptor descriptor, [CallerMemberName] string testMethod = "") + => RunTest(descriptor, Array.Empty(), testMethod); + + private Task RunTest(DiagnosticDescriptor descriptor, int statusCode, [CallerMemberName] string testMethod = "") + => RunTest(descriptor, new[] { statusCode.ToString() }, testMethod); + + private async Task RunTest(DiagnosticDescriptor descriptor, object[] args, [CallerMemberName] string testMethod = "") + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Executor.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Collection( + result, + diagnostic => + { + Assert.Equal(descriptor.Id, diagnostic.Id); + Assert.Same(descriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), args), diagnostic.GetMessage()); + }); + } + + private async Task RunTestFor1006(int statusCode, [CallerMemberName] string testMethod = "") + { + // Arrange + var descriptor = DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode; + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + var executor = new ApiCoventionWith1006DiagnosticEnabledRunner(); + + // Act + var result = await executor.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Collection( + result, + diagnostic => + { + Assert.Equal(descriptor.Id, diagnostic.Id); + Assert.Same(descriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), new[] { statusCode.ToString() }), diagnostic.GetMessage()); + }); + } + + private class ApiCoventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner + { + public ApiCoventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer()) + { + } + + protected override CompilationOptions ConfigureCompilationOptions(CompilationOptions options) + { + var compilationOptions = base.ConfigureCompilationOptions(options); + var specificDiagnosticOptions = compilationOptions.SpecificDiagnosticOptions.Add( + DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode.Id, + ReportDiagnostic.Info); + + return compilationOptions.WithSpecificDiagnosticOptions(specificDiagnosticOptions); + } + } + } +} diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs new file mode 100644 index 0000000000..65283bc5f8 --- /dev/null +++ b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs @@ -0,0 +1,297 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; +using static Microsoft.AspNetCore.Mvc.Analyzers.ApiConventionAnalyzer; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class ApiConventionAnalyzerTest + { + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingStatusCodeConstants() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingStatusCodesConstants).FullName).GetAttributes()[0]; + + // Act + var actual = ApiConventionAnalyzer.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(412, actual); + } + + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingHttpStatusCast() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingHttpStatusCodeCast).FullName).GetAttributes()[0]; + + // Act + var actual = ApiConventionAnalyzer.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(302, actual); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNull_ForReturnTypeIf200StatusCodeIsDeclared() + { + // Arrange + var compilation = await GetCompilation(); + + var returnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); + var context = GetContext(compilation, new[] { 200 }); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, returnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNull_ForReturnTypeIf201StatusCodeIsDeclared() + { + // Arrange + var compilation = await GetCompilation(); + + var returnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); + var context = GetContext(compilation, new[] { 201 }); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, returnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNull_ForDerivedReturnTypeIf200StatusCodeIsDeclared() + { + // Arrange + var compilation = await GetCompilation(); + + var declaredReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); + var context = GetContext(compilation, new[] { 201 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerDerivedModel).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsDiagnostic_If200IsNotDocumented() + { + // Arrange + var compilation = await GetCompilation(); + + var context = GetContext(compilation, new[] { 404 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerDerivedModel).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.NotNull(diagnostic); + Assert.Same(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, diagnostic.Descriptor); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsDiagnostic_IfReturnTypeIsActionResultReturningUndocumentedStatusCode() + { + // Arrange + var compilation = await GetCompilation(); + + var declaredReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); + var context = GetContext(compilation, new[] { 200, 404 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(BadRequestObjectResult).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.NotNull(diagnostic); + Assert.Same(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, diagnostic.Descriptor); + } + + [Fact] + public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfReturnTypeDoesNotHaveStatusCodeAttribute() + { + // Arrange + var compilation = await GetCompilation(); + + var context = GetContext(compilation, new[] { 200, 404 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(EmptyResult).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfDeclaredAndActualReturnTypeAreIActionResultInstances() + { + // Arrange + var compilation = await GetCompilation(); + + var declaredReturnType = compilation.GetTypeByMetadataName(typeof(IActionResult).FullName); + var context = GetContext(compilation, new[] { 404 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(EmptyResult).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfDeclaredAndActualReturnTypeAreIActionResult() + { + // Arrange + var compilation = await GetCompilation(); + + var context = GetContext(compilation, new[] { 404 }); + var actualReturnType = compilation.GetTypeByMetadataName(typeof(IActionResult).FullName); + + // Act + var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); + + // Assert + Assert.Null(diagnostic); + } + + [Fact] + public async Task ShouldEvaluateMethod_ReturnsFalse_IfMethodReturnTypeIsInvalid() + { + // Arrange + var source = @" +using Microsoft.AspNetCore.Mvc; + +namespace TestNamespace +{ + [ApiController] + public class TestController : ControllerBase + { + public DoesNotExist Get(int id) + { + if (id == 0) + { + return NotFound(); + } + + return new DoesNotExist(id); + } + } +}"; + var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); + var compilation = await project.GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + var method = (IMethodSymbol)compilation.GetTypeByMetadataName("TestNamespace.TestController").GetMembers("Get").First(); + + // Act + var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotController() + { + // Arrange + var compilation = await GetCompilation(); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_IndexModel).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_IndexModel.OnGet)).First(); + + // Act + var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotApiController() + { + // Arrange + var compilation = await GetCompilation(); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_NotApiController).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_NotApiController.Index)).First(); + + // Act + var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotAction() + { + // Arrange + var compilation = await GetCompilation(); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_NotAction).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_NotAction.Index)).First(); + + // Act + var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task ShouldEvaluateMethod_ReturnsTrue_ForValidActionMethods() + { + // Arrange + var compilation = await GetCompilation(); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerTest_Valid).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_Valid.Index)).First(); + + // Act + var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + + // Assert + Assert.True(result); + } + + private static ApiConventionContext GetContext(Compilation compilation, int[] expectedStatusCodes) + { + var symbolCache = new ApiControllerSymbolCache(compilation); + var context = new ApiConventionContext( + symbolCache, + default, + expectedStatusCodes.Select(s => new ApiResponseMetadata(s, null, null)).ToArray(), + new HashSet()); + return context; + } + + private Task GetCompilation() + { + var testSource = MvcTestSource.Read(GetType().Name, "ApiConventionAnalyzerTestFile"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs index 392a16d6bb..6c5a5a2937 100644 --- a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs +++ b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs @@ -67,6 +67,24 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers attributeData => Assert.Equal(400, attributeData.ConstructorArguments[0].Value)); } + [Fact] + public async Task GetAttributesSymbolOverload_OnMethodSymbol() + { + // Arrange + var compilation = await GetCompilation("GetAttributes_WithMethodOverridding"); + var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithInheritFalse_ReturnsAllAttributesOnCurrentActionClass.Method)).First(); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(symbol: method, attribute: attribute); + + // Assert + Assert.Collection( + attributes, + attributeData => Assert.Equal(400, attributeData.ConstructorArguments[0].Value)); + } + [Fact] public async Task GetAttributes_WithInheritTrue_ReturnsAllAttributesOnCurrentActionAndOverridingMethod() { @@ -123,6 +141,120 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers attributeData => Assert.Equal(401, attributeData.ConstructorArguments[0].Value)); } + [Fact] + public async Task GetAttributes_OnTypeWithoutAttributes() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(ApiConventionTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName(typeof(GetAttributes_OnTypeWithoutAttributesType).FullName); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(testClass, attribute, inherit: true); + + // Assert + Assert.Empty(attributes); + } + + [Fact] + public async Task GetAttributes_OnTypeWithAttributes() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(ApiConventionTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName(typeof(GetAttributes_OnTypeWithAttributes).FullName); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(testClass, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_Object)); + }, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_String)); + }); + } + + [Fact] + public async Task GetAttributes_BaseTypeWithAttributes() + { + // Arrange + var compilation = await GetCompilation(); + var attribute = compilation.GetTypeByMetadataName(typeof(ApiConventionTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName(typeof(GetAttributes_BaseTypeWithAttributesDerived).FullName); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(testClass, attribute, inherit: true); + + // Assert + Assert.Collection( + attributes, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_Int32)); + }, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_Object)); + }, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_String)); + }); + } + + [Fact] + public async Task GetAttributes_OnDerivedTypeWithInheritFalse() + { + // Arrange + var compilation = await GetCompilation(nameof(GetAttributes_BaseTypeWithAttributes)); + var attribute = compilation.GetTypeByMetadataName(typeof(ApiConventionTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName(typeof(GetAttributes_BaseTypeWithAttributesDerived).FullName); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(testClass, attribute, inherit: false); + + // Assert + Assert.Collection( + attributes, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_Int32)); + }); + } + + [Fact] + public async Task GetAttributesSymbolOverload_OnTypeSymbol() + { + // Arrange + var compilation = await GetCompilation(nameof(GetAttributes_BaseTypeWithAttributes)); + var attribute = compilation.GetTypeByMetadataName(typeof(ApiConventionTypeAttribute).FullName); + var testClass = compilation.GetTypeByMetadataName(typeof(GetAttributes_BaseTypeWithAttributesDerived).FullName); + + // Act + var attributes = CodeAnalysisExtensions.GetAttributes(symbol: testClass, attribute: attribute); + + // Assert + Assert.Collection( + attributes, + attributeData => + { + Assert.Same(attribute, attributeData.AttributeClass); + Assert.Equal(attributeData.ConstructorArguments[0].Value, compilation.GetSpecialType(SpecialType.System_Int32)); + }); + } + [Fact] public async Task HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute() { @@ -325,6 +457,21 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers Assert.False(isAssignableFromDerived); // Inverse shouldn't be true } + [Fact] + public async Task IsAssignable_ReturnsTrue_IfSourceAndDestinationAreTheSameInterface() + { + // Arrange + var compilation = await GetCompilation(nameof(IsAssignable_ReturnsTrueIfTypeImplementsInterface)); + var source = compilation.GetTypeByMetadataName(typeof(IsAssignable_ReturnsTrueIfTypeImplementsInterface).FullName); + var target = compilation.GetTypeByMetadataName(typeof(IsAssignable_ReturnsTrueIfTypeImplementsInterface).FullName); + + // Act + var isAssignableFrom = CodeAnalysisExtensions.IsAssignableFrom(source, target); + + // Assert + Assert.True(isAssignableFrom); + } + [Fact] public async Task IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface() { diff --git a/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj b/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj index 92de24ec50..275232d4ab 100644 --- a/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj +++ b/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj @@ -3,6 +3,7 @@ $(StandardTestTfms) true + Microsoft.AspNetCore.Mvc.Analyzers diff --git a/test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs b/test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs new file mode 100644 index 0000000000..34441fc218 --- /dev/null +++ b/test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.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; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; +using static Microsoft.AspNetCore.Mvc.Analyzers.SymbolApiConventionMatcher; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class SymbolApiConventionMatcherTest + { + private static readonly string BaseTypeName = typeof(Base).FullName; + private static readonly string DerivedTypeName = typeof(Derived).FullName; + private static readonly string TestControllerName = typeof(TestController).FullName; + private static readonly string TestConventionName = typeof(TestConvention).FullName; + + [Theory] + [InlineData("Method", "method")] + [InlineData("Method", "ConventionMethod")] + [InlineData("p", "model")] + [InlineData("person", "model")] + public void IsNameMatch_WithAny_AlwaysReturnsTrue(string name, string conventionName) + { + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "Name"; + var conventionName = "name"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "Name"; + var conventionName = "Different"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSubString() + { + // Arrange + var name = "RegularName"; + var conventionName = "Regular"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsFalse_IfConventionNameIsSuperString() + { + // Arrange + var name = "Regular"; + var conventionName = "RegularName"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithExact_ReturnsTrue_IfExactMatch() + { + // Arrange + var name = "parameterName"; + var conventionName = "parameterName"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Exact); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNamesAreExact() + { + // Arrange + var name = "PostPerson"; + var conventionName = "PostPerson"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsTrue_IfNameIsProperPrefix() + { + // Arrange + var name = "PostPerson"; + var conventionName = "Post"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "GetPerson"; + var conventionName = "Post"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNamesDifferInCase() + { + // Arrange + var name = "GetPerson"; + var conventionName = "post"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() + { + // Arrange + var name = "Postman"; + var conventionName = "Post"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsSuffix() + { + // Arrange + var name = "GoPost"; + var conventionName = "Post"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Prefix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNamesAreDifferent() + { + // Arrange + var name = "name"; + var conventionName = "diff"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnsFalse_IfNameIsNotSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "idx"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsExact() + { + // Arrange + var name = "test"; + var conventionName = "test"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameDiffersInCase() + { + // Arrange + var name = "test"; + var conventionName = "Test"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Fact] + public void IsNameMatch_WithSuffix_ReturnTrue_IfNameIsProperSuffix() + { + // Arrange + var name = "personId"; + var conventionName = "id"; + + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.True(result); + } + + [Theory] + [InlineData("candid", "id")] + [InlineData("canDid", "id")] + public void IsNameMatch_WithSuffix_ReturnFalse_IfNameIsNotProperSuffix(string name, string conventionName) + { + // Act + var result = IsNameMatch(name, conventionName, SymbolApiConventionNameMatchBehavior.Suffix); + + // Assert + Assert.False(result); + } + + [Theory] + [InlineData(typeof(object), typeof(object))] + [InlineData(typeof(int), typeof(void))] + [InlineData(typeof(string), typeof(DateTime))] + public async Task IsTypeMatch_WithAny_ReturnsTrue(Type type, Type conventionType) + { + // Arrange + var compilation = await GetCompilationAsync(); + var typeSymbol = compilation.GetTypeByMetadataName(type.FullName); + var conventionTypeSymbol = compilation.GetTypeByMetadataName(conventionType.FullName); + + // Act + var result = IsTypeMatch(typeSymbol, conventionTypeSymbol, SymbolApiConventionTypeMatchBehavior.Any); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task IsTypeMatch_WithAssignableFrom_ReturnsTrueForExact() + { + // Arrange + var compilation = await GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName(BaseTypeName); + var conventionType = compilation.GetTypeByMetadataName(BaseTypeName); + + // Act + var result = IsTypeMatch(type, conventionType, SymbolApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task IsTypeMatch_WithAssinableFrom_ReturnsTrueForDerived() + { + // Arrange + var compilation = await GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName(DerivedTypeName); + var conventionType = compilation.GetTypeByMetadataName(BaseTypeName); + + + // Act + var result = IsTypeMatch(type, conventionType, SymbolApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.True(result); + } + + [Fact] + public async Task IsTypeMatch_WithAssinableFrom_ReturnsFalseForBaseTypes() + { + // Arrange + var compilation = await GetCompilationAsync(); + + var type = compilation.GetTypeByMetadataName(BaseTypeName); + var conventionType = compilation.GetTypeByMetadataName(DerivedTypeName); + + // Act + var result = IsTypeMatch(type, conventionType, SymbolApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public async Task IsTypeMatch_WithAssinableFrom_ReturnsFalseForUnrelated() + { + // Arrange + var compilation = await GetCompilationAsync(); + + var type = compilation.GetSpecialType(SpecialType.System_String); + var conventionType = compilation.GetTypeByMetadataName(BaseTypeName); + + // Act + var result = IsTypeMatch(type, conventionType, SymbolApiConventionTypeMatchBehavior.AssignableFrom); + + // Assert + Assert.False(result); + } + + [Fact] + public Task IsMatch_ReturnsFalse_IfMethodNamesDoNotMatch() + { + // Arrange + var methodName = nameof(TestController.Get); + var conventionMethodName = nameof(TestConvention.Post); + var expected = false; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_ReturnsFalse_IMethodHasMoreParametersThanConvention() + { + // Arrange + var methodName = nameof(TestController.Get); + var conventionMethodName = nameof(TestConvention.GetNoArgs); + var expected = false; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_ReturnsFalse_IfMethodHasFewerParametersThanConvention() + { + // Arrange + var methodName = nameof(TestController.Get); + var conventionMethodName = nameof(TestConvention.GetTwoArgs); + var expected = false; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_ReturnsFalse_IfParametersDoNotMatch() + { + // Arrange + var methodName = nameof(TestController.Get); + var conventionMethodName = nameof(TestConvention.GetParameterNotMatching); + var expected = false; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() + { + // Arrange + var methodName = nameof(TestController.Get); + var conventionMethodName = nameof(TestConvention.Get); + var expected = true; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_ReturnsTrue_IfParamsArrayMatchesRemainingArguments() + { + // Arrange + var methodName = nameof(TestController.Search); + var conventionMethodName = nameof(TestConvention.Search); + var expected = true; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + [Fact] + public Task IsMatch_WithEmpty_MatchesMethodWithNoParameters() + { + // Arrange + var methodName = nameof(TestController.SearchEmpty); + var conventionMethodName = nameof(TestConvention.SearchWithParams); + var expected = true; + + return RunMatchTest(methodName, conventionMethodName, expected); + } + + private async Task RunMatchTest(string methodName, string conventionMethodName, bool expected) + { + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testController = compilation.GetTypeByMetadataName(TestControllerName); + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = (IMethodSymbol)testController.GetMembers(methodName).First(); + var conventionMethod = (IMethodSymbol)testConvention.GetMembers(conventionMethodName).First(); + + // Act + var result = IsMatch(symbolCache, method, conventionMethod); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetNameMatchBehavior_ReturnsExact_WhenNoAttributesArePresent() + { + // Arrange + var expected = SymbolApiConventionNameMatchBehavior.Exact; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = testConvention.GetMembers(nameof(TestConvention.MethodWithoutMatchBehavior)).First(); + + // Act + var result = GetNameMatchBehavior(symbolCache, method); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetNameMatchBehavior_ReturnsExact_WhenNoNameMatchBehaviorAttributeIsSpecified() + { + // Arrange + var expected = SymbolApiConventionNameMatchBehavior.Exact; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = testConvention.GetMembers(nameof(TestConvention.MethodWithRandomAttributes)).First(); + + // Act + var result = GetNameMatchBehavior(symbolCache, method); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetNameMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = SymbolApiConventionNameMatchBehavior.Prefix; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = testConvention.GetMembers(nameof(TestConvention.Get)).First(); + + // Act + var result = GetNameMatchBehavior(symbolCache, method); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoAttributesArePresent() + { + // Arrange + var expected = SymbolApiConventionTypeMatchBehavior.AssignableFrom; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.Get)).First(); + var parameter = method.Parameters[0]; + + // Act + var result = GetTypeMatchBehavior(symbolCache, parameter); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetTypeMatchBehavior_ReturnsIsAssignableFrom_WhenNoMatchingAttributesArePresent() + { + // Arrange + var expected = SymbolApiConventionTypeMatchBehavior.AssignableFrom; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.MethodParameterWithRandomAttributes)).First(); + var parameter = method.Parameters[0]; + + // Act + var result = GetTypeMatchBehavior(symbolCache, parameter); + + // Assert + Assert.Equal(expected, result); + } + + [Fact] + public async Task GetTypeMatchBehavior_ReturnsValueFromAttributes() + { + // Arrange + var expected = SymbolApiConventionTypeMatchBehavior.Any; + var compilation = await GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var testConvention = compilation.GetTypeByMetadataName(TestConventionName); + var method = (IMethodSymbol)testConvention.GetMembers(nameof(TestConvention.MethodWithAnyTypeMatchBehaviorParameter)).First(); + var parameter = method.Parameters[0]; + + // Act + var result = GetTypeMatchBehavior(symbolCache, parameter); + + // Assert + Assert.Equal(expected, result); + } + + private Task GetCompilationAsync(string test = "SymbolApiConventionMatcherTestFile") + { + var testSource = MvcTestSource.Read(GetType().Name, test); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} \ No newline at end of file diff --git a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index 3f7aad5564..c31846a88d 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -1,6 +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 System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; @@ -21,10 +22,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.GetPerson)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -37,10 +38,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerWithoutConvention)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerWithoutConvention.PostPerson)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -53,10 +54,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesAttribute)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -69,10 +70,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeInConstructor)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -92,10 +93,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructor)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -115,10 +116,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeInConstructorAndProperty)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -138,10 +139,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseType_StatusCodeAndTypeInConstructorAndProperty)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -161,10 +162,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithArguments)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -184,10 +185,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomApiResponseMetadataProvider)).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -215,10 +216,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{typeName}"); var method = (IMethodSymbol)controller.GetMembers(methodName).First(); - var typeCache = new ApiControllerTypeCache(compilation); + var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(typeCache, method); + var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs new file mode 100644 index 0000000000..2e0fc59064 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs @@ -0,0 +1,21 @@ +using System; +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes : ControllerBase + { + public ActionResult Method(Guid? id) + { + if (id == null) + { + /*MM*/return NotFound(); + } + + return "Hello world"; + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs new file mode 100644 index 0000000000..7d6169af7e --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + [ProducesResponseType(typeof(string), 404)] + public IActionResult Put(int id, object model) + { + if (id == 0) + { + return NotFound(); + } + + if (!ModelState.IsValid) + { + /*MM*/return UnprocessableEntity(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs new file mode 100644 index 0000000000..07ea47c1a4 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs @@ -0,0 +1,50 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Analyzers; + +[assembly: ApiConventionType(typeof(DiagnosticsAreReturned_ForControllerWithCustomConvention))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_ForControllerWithCustomConventionController : ControllerBase + { + public async Task Update(int id, Product product) + { + if (id < 0) + { + /*MM*/return BadRequest(); + } + + try + { + await product.Update(); + + } + catch + { + return Conflict(); + } + + return Ok(); + } + } + + public static class DiagnosticsAreReturned_ForControllerWithCustomConvention + { + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status409Conflict)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + public static void Update(int id, Product product) + { + + } + } + + public class Product + { + public Task Update() => Task.CompletedTask; + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs new file mode 100644 index 0000000000..ce686e8dcf --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase + { + [ProducesResponseType(typeof(string), 404)] + public async ValueTask Method(int id) + { + await Task.Yield(); + if (id == 0) + { + return NotFound(); + } + + /*MM*/return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs new file mode 100644 index 0000000000..eb8f5cd36a --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -0,0 +1,21 @@ +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + public async Task Method(int id) + { + await Task.Yield(); + if (id == 0) + { + /*MM*/return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs new file mode 100644 index 0000000000..b27ed2dc61 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation : ControllerBase + { + [ProducesResponseType(404)] + public async Task> Method(int id) + { + await Task.Yield(); + + if (id == 0) + { + return NotFound(); + } + + /*MM*/return new DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentationModel(); + } + } + + public class DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentationModel { } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs new file mode 100644 index 0000000000..3de2b09935 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs @@ -0,0 +1,19 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation : ControllerBase + { + [ProducesResponseType(404)] + public ActionResult Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + /*MM*/return new DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentationModel(); + } + } + + public class DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentationModel { } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs new file mode 100644 index 0000000000..ef4bf9ff03 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs @@ -0,0 +1,23 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType : ControllerBase + { + [ProducesResponseType(404)] + public ActionResult Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + /*MM*/return new DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedTypeDerived(); + } + } + + public class DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedTypeBaseModel { } + + public class DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedTypeDerived : DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedTypeBaseModel { } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs new file mode 100644 index 0000000000..fb880c322b --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode : ControllerBase + { + public IActionResult /*MM*/Delete(int id) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs new file mode 100644 index 0000000000..a338f55d1a --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode : ControllerBase + { + public IActionResult Get(int id) + { + if (id < 0) + { + /*MM*/return BadRequest(); + } + + if (id == 0) + { + return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs new file mode 100644 index 0000000000..8893357089 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode : ControllerBase + { + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesResponseType(404)] + public IActionResult /*MM*/Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs new file mode 100644 index 0000000000..2541e2f98e --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + public IActionResult Method(int id) + { + if (id == 0) + { + /*MM*/return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs new file mode 100644 index 0000000000..ad6e5956b4 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs @@ -0,0 +1,12 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred : ControllerBase + { + [ProducesResponseType(201)] + public IActionResult Method(int id) + { + return id == 0 ? (IActionResult)NotFound() : Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs new file mode 100644 index 0000000000..06bb57f1ee --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + [ProducesResponseType(typeof(string), 400)] + [ProducesResponseType(typeof(string), 404)] + public IActionResult Put(int id, object model) + { + if (id == 0) + { + return NotFound(); + } + + if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs new file mode 100644 index 0000000000..0e55a8c574 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class NoDiagnosticsAreReturned_ForNonApiController : Controller + { + [ProducesResponseType(typeof(string), 200)] + public IActionResult Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs new file mode 100644 index 0000000000..9299b5b5b8 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs @@ -0,0 +1,18 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class Home : PageModel + { + [ProducesResponseType(302)] + public IActionResult OnPost(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Page(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs new file mode 100644 index 0000000000..bd62df3b3b --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs @@ -0,0 +1,41 @@ +using System; +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForReturnStatementsInLambdas : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + [ProducesResponseType(typeof(string), 404)] + public IActionResult Put(int id, object model) + { + Func someLambda = () => + { + if (id < -1) + { + // We should not process this. + return UnprocessableEntity(); + } + + return null; + }; + + + if (id == 0) + { + return NotFound(); + } + + + if (id == 1) + { + return someLambda(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs new file mode 100644 index 0000000000..332de79235 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs @@ -0,0 +1,34 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions : ControllerBase + { + [ProducesResponseType(typeof(string), 200)] + [ProducesResponseType(typeof(string), 404)] + public IActionResult Put(int id, object model) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return LocalFunction(); + } + + return Ok(); + + IActionResult LocalFunction() + { + if (id < -1) + { + // We should not process this. + return UnprocessableEntity(); + } + + return null; + } + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs new file mode 100644 index 0000000000..c4d6a646a9 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs @@ -0,0 +1,56 @@ +using System.Collections.Generic; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class UnwrapMethodReturnType + { + public ApiConventionAnalyzerBaseModel ReturnsBaseModel() => null; + + public ActionResult ReturnsActionResultOfBaseModel() => null; + + public Task> ReturnsTaskOfActionResultOfBaseModel() => null; + + public ValueTask> ReturnsValueTaskOfActionResultOfBaseModel() => default(ValueTask>); + + public ActionResult> ReturnsActionResultOfIEnumerableOfBaseModel() => null; + + public IEnumerable ReturnsIEnumerableOfBaseModel() => null; + } + + [DefaultStatusCode(StatusCodes.Status412PreconditionFailed)] + public class TestActionResultUsingStatusCodesConstants { } + + [DefaultStatusCode((int)HttpStatusCode.Found)] + public class TestActionResultUsingHttpStatusCodeCast { } + + public class ApiConventionAnalyzerBaseModel { } + + public class ApiConventionAnalyzerDerivedModel : ApiConventionAnalyzerBaseModel { } + + public class ApiConventionAnalyzerTest_IndexModel : PageModel + { + public IActionResult OnGet() => null; + } + + public class ApiConventionAnalyzerTest_NotApiController : Controller + { + public IActionResult Index() => null; + } + + public class ApiConventionAnalyzerTest_NotAction : Controller + { + [NonAction] + public IActionResult Index() => null; + } + + [ApiController] + public class ApiConventionAnalyzerTest_Valid : Controller + { + public IActionResult Index() => null; + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs new file mode 100644 index 0000000000..8e0ee1e5dc --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiConventionType(typeof(object))] + [ApiController] + [ApiConventionType(typeof(string))] + public class GetAttributes_BaseTypeWithAttributesBase + { + } + + [ApiConventionType(typeof(int))] + public class GetAttributes_BaseTypeWithAttributesDerived : GetAttributes_BaseTypeWithAttributesBase + { + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs new file mode 100644 index 0000000000..a402f5cea9 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiConventionType(typeof(object))] + [ApiController] + [ApiConventionType(typeof(string))] + public class GetAttributes_OnTypeWithAttributes + { + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs new file mode 100644 index 0000000000..e4aea55674 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs @@ -0,0 +1,8 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class GetAttributes_OnTypeWithoutAttributesType + { + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs new file mode 100644 index 0000000000..9e549f8016 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs @@ -0,0 +1,55 @@ +using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.ApiExplorer; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class Base { } + + public class Derived : Base { } + + public class TestController + { + public IActionResult Get(int id) => null; + + public IActionResult Search(string searchTerm, bool sortDescending, int page) => null; + + public IActionResult SearchEmpty() => null; + } + + public static class TestConvention + { + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Get(int id) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetNoArgs() { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void GetTwoArgs(int id, string name) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void Post(Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Prefix)] + public static void GetParameterNotMatching([ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.AssignableFrom)] Derived model) { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void Search( + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Exact)] + string searchTerm, + params object[] others) + { } + + [ApiConventionNameMatch(ApiConventionNameMatchBehavior.Any)] + public static void SearchWithParams(params object[] others) { } + + public static void MethodWithoutMatchBehavior() { } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void MethodWithRandomAttributes() { } + + public static void MethodParameterWithRandomAttributes([FromRoute] int value) { } + + public static void MethodWithAnyTypeMatchBehaviorParameter([ApiConventionTypeMatch(ApiConventionTypeMatchBehavior.Any)] int value) { } + } +} From a5083d525b76ed8699d2b94e91d7ef52106d65aa Mon Sep 17 00:00:00 2001 From: Hao Kung Date: Thu, 12 Jul 2018 15:36:03 -0700 Subject: [PATCH 096/316] Fix regression with Authorize + IPolicyProvider (#8068) --- .../Authorization/AuthorizeFilter.cs | 47 +++++++----- .../Authorization/AuthorizeFilterTest.cs | 75 +++++++++++++++++++ .../AuthorizeFilterIntegrationTest.cs | 44 ++++++++++- 3 files changed, 142 insertions(+), 24 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs index 727ac3d659..29cf7cc085 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs @@ -108,6 +108,24 @@ namespace Microsoft.AspNetCore.Mvc.Authorization bool IFilterFactory.IsReusable => true; + // Computes the actual policy for this filter using either Policy or PolicyProvider + AuthorizeData + private Task ComputePolicyAsync() + { + if (Policy != null) + { + return Task.FromResult(Policy); + } + if (PolicyProvider == null) + { + throw new InvalidOperationException( + Resources.FormatAuthorizeFilter_AuthorizationPolicyCannotBeCreated( + nameof(AuthorizationPolicy), + nameof(IAuthorizationPolicyProvider))); + } + + return AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData); + } + private async Task GetEffectivePolicyAsync(AuthorizationFilterContext context) { if (_effectivePolicy != null) @@ -115,7 +133,8 @@ namespace Microsoft.AspNetCore.Mvc.Authorization return _effectivePolicy; } - var effectivePolicy = Policy; + var effectivePolicy = await ComputePolicyAsync(); + var canCache = PolicyProvider == null; if (_mvcOptions == null) { @@ -124,13 +143,13 @@ namespace Microsoft.AspNetCore.Mvc.Authorization if (_mvcOptions.AllowCombiningAuthorizeFilters) { - if (!context.IsEffectivePolicy(this)) + if (!context.IsEffectivePolicy(this)) { return null; } // Combine all authorize filters into single effective policy that's only run on the closest filter - AuthorizationPolicyBuilder builder = null; + var builder = new AuthorizationPolicyBuilder(effectivePolicy); for (var i = 0; i < context.Filters.Count; i++) { if (ReferenceEquals(this, context.Filters[i])) @@ -140,29 +159,17 @@ namespace Microsoft.AspNetCore.Mvc.Authorization if (context.Filters[i] is AuthorizeFilter authorizeFilter) { - builder = builder ?? new AuthorizationPolicyBuilder(effectivePolicy); - builder.Combine(authorizeFilter.Policy); + // Combine using the explicit policy, or the dynamic policy provider + builder.Combine(await authorizeFilter.ComputePolicyAsync()); + canCache = canCache && authorizeFilter.PolicyProvider == null; } } effectivePolicy = builder?.Build() ?? effectivePolicy; } - if (effectivePolicy == null) - { - if (PolicyProvider == null) - { - throw new InvalidOperationException( - Resources.FormatAuthorizeFilter_AuthorizationPolicyCannotBeCreated( - nameof(AuthorizationPolicy), - nameof(IAuthorizationPolicyProvider))); - } - - effectivePolicy = await AuthorizationPolicy.CombineAsync(PolicyProvider, AuthorizeData); - } - - // We can cache the effective policy when there is no custom policy provider - if (PolicyProvider == null) + // We can cache the effective policy when there is no custom policy provider + if (canCache) { _effectivePolicy = effectivePolicy; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs index 765fccd0e3..fdde1514de 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs @@ -221,6 +221,81 @@ namespace Microsoft.AspNetCore.Mvc.Authorization Assert.Null(authorizationContext.Result); } + private class TestPolicyProvider : IAuthorizationPolicyProvider + { + private AuthorizationPolicy _true = new AuthorizationPolicyBuilder().RequireAssertion(_ => true).Build(); + private AuthorizationPolicy _false = new AuthorizationPolicyBuilder().RequireAssertion(_ => false).Build(); + + public int GetPolicyCalls = 0; + + public Task GetDefaultPolicyAsync() + => Task.FromResult(_true); + + public Task GetPolicyAsync(string policyName) + { + GetPolicyCalls++; + return Task.FromResult(policyName == "true" ? _true : _false); + } + } + + [Fact] + public async Task AuthorizationFilterCombinesMultipleFiltersWithPolicyProvider() + { + // Arrange + var authorizeFilter = new AuthorizeFilter(new TestPolicyProvider(), new IAuthorizeData[] + { + new AuthorizeAttribute { Policy = "true"}, + new AuthorizeAttribute { Policy = "false"} + }); + var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure(o => o.AllowCombiningAuthorizeFilters = true)); + // Effective policy should fail, if both are combined + authorizationContext.Filters.Add(authorizeFilter); + var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); + authorizationContext.Filters.Add(secondFilter); + + // Act + await secondFilter.OnAuthorizationAsync(authorizationContext); + + // Assert + Assert.IsType(authorizationContext.Result); + } + + [Fact] + public async Task AuthorizationFilterCombinesMultipleFiltersWithDifferentPolicyProvider() + { + // Arrange + var testProvider1 = new TestPolicyProvider(); + var testProvider2 = new TestPolicyProvider(); + var authorizeFilter = new AuthorizeFilter(testProvider1, new IAuthorizeData[] + { + new AuthorizeAttribute { Policy = "true"}, + new AuthorizeAttribute { Policy = "false"} + }); + var authorizationContext = GetAuthorizationContext(anonymous: false, registerServices: s => s.Configure(o => o.AllowCombiningAuthorizeFilters = true)); + // Effective policy should fail, if both are combined + authorizationContext.Filters.Add(authorizeFilter); + var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); + authorizationContext.Filters.Add(secondFilter); + var thirdFilter = new AuthorizeFilter(testProvider2, new IAuthorizeData[] { new AuthorizeAttribute(policy: "something") }); + authorizationContext.Filters.Add(thirdFilter); + + // Act + await thirdFilter.OnAuthorizationAsync(authorizationContext); + + // Assert + Assert.IsType(authorizationContext.Result); + Assert.Equal(2, testProvider1.GetPolicyCalls); + Assert.Equal(1, testProvider2.GetPolicyCalls); + + // Make sure the policy calls are not cached + await thirdFilter.OnAuthorizationAsync(authorizationContext); + + // Assert + Assert.IsType(authorizationContext.Result); + Assert.Equal(4, testProvider1.GetPolicyCalls); + Assert.Equal(2, testProvider2.GetPolicyCalls); + } + [Fact] public async Task AuthorizationFilterCombinesMultipleFilters() { diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs index 1134aa2f73..596d5b8ab3 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs @@ -54,11 +54,47 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Equal(2, policyProvider.GetPolicyCount); } - private HttpContext GetHttpContext() + // This is a test for security, because we can't assume that any IAuthorizationPolicyProvider other than + // DefaultAuthorizationPolicyProvider will return the same result for the same input. So a cache could cause + // undesired access. + [Fact] + public async Task CombinedAuthorizeFilter_AlwaysCalledWithNonDefaultProvider() + { + // Arrange + var applicationModelProviderContext = GetProviderContext(typeof(AuthorizeController)); + + var policyProvider = new TestAuthorizationPolicyProvider(); + + var controller = Assert.Single(applicationModelProviderContext.Result.Controllers); + var action = Assert.Single(controller.Actions); + var authorizeData = action.Attributes.OfType(); + var authorizeFilter = new AuthorizeFilter(policyProvider, authorizeData); + + var actionContext = new ActionContext(GetHttpContext(combineAuthorize: true), new RouteData(), new ControllerActionDescriptor()); + + var authorizationFilterContext = new AuthorizationFilterContext(actionContext, action.Filters); + + authorizationFilterContext.Filters.Add(authorizeFilter); + + var secondFilter = new AuthorizeFilter(new AuthorizationPolicyBuilder().RequireAssertion(a => true).Build()); + authorizationFilterContext.Filters.Add(secondFilter); + + var thirdFilter = new AuthorizeFilter(policyProvider, authorizeData); + authorizationFilterContext.Filters.Add(thirdFilter); + + // Act + await thirdFilter.OnAuthorizationAsync(authorizationFilterContext); + await thirdFilter.OnAuthorizationAsync(authorizationFilterContext); + + // Assert + Assert.Equal(4, policyProvider.GetPolicyCount); + } + + private HttpContext GetHttpContext(bool combineAuthorize = false) { var httpContext = new DefaultHttpContext(); - httpContext.RequestServices = GetServices(); + httpContext.RequestServices = GetServices(combineAuthorize); return httpContext; } @@ -73,11 +109,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests return context; } - private static IServiceProvider GetServices() + private static IServiceProvider GetServices(bool combineAuthorize) { var serviceCollection = new ServiceCollection(); serviceCollection.AddAuthorization(); - serviceCollection.AddMvc(); + serviceCollection.AddMvc(o => o.AllowCombiningAuthorizeFilters = combineAuthorize); serviceCollection .AddSingleton(NullLoggerFactory.Instance) .AddTransient, Logger>() From f12f9b46ed5cc848a36db1fa9933c35b3eb60351 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 13 Jul 2018 12:23:30 +1200 Subject: [PATCH 097/316] Add startup filter to set MiddlewareFilterBuilder.ApplicationBuilder --- .../MvcApplicationBuilderExtensions.cs | 3 --- .../MvcCoreServiceCollectionExtensions.cs | 2 ++ .../MiddlewareFilterBuilderStartupFilter.cs | 26 +++++++++++++++++++ .../MvcCoreServiceCollectionExtensionsTest.cs | 15 ++++++++--- 4 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index c35d40d0f1..74e5f8e5ec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -79,9 +79,6 @@ namespace Microsoft.AspNetCore.Builder VerifyMvcIsRegistered(app); - var middlewarePipelineBuilder = app.ApplicationServices.GetRequiredService(); - middlewarePipelineBuilder.ApplicationBuilder = app.New(); - var routes = new RouteBuilder(app) { DefaultHandler = app.ApplicationServices.GetRequiredService(), diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 84b77646bc..2f48bbedb0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -273,6 +273,8 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); // This maintains a cache of middleware pipelines, so it needs to be a singleton services.TryAddSingleton(); + // Sets ApplicationBuilder on MiddlewareFilterBuilder + services.TryAddEnumerable(ServiceDescriptor.Singleton()); } private static void ConfigureDefaultServices(IServiceCollection services) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs new file mode 100644 index 0000000000..d568da7b51 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.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; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal class MiddlewareFilterBuilderStartupFilter : IStartupFilter + { + public Action Configure(Action next) + { + return MiddlewareFilterBuilder; + + void MiddlewareFilterBuilder(IApplicationBuilder builder) + { + var middlewarePipelineBuilder = builder.ApplicationServices.GetRequiredService(); + middlewarePipelineBuilder.ApplicationBuilder = builder.New(); + + next(builder); + } + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index e2e7412818..3e6c558518 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc var services = new ServiceCollection(); // Register a mock implementation of each service, AddMvcServices should add another implementation. - foreach (var serviceType in MutliRegistrationServiceTypes) + foreach (var serviceType in MultiRegistrationServiceTypes) { var mockType = typeof(Mock<>).MakeGenericType(serviceType.Key); services.Add(ServiceDescriptor.Transient(serviceType.Key, mockType)); @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); // Assert - foreach (var serviceType in MutliRegistrationServiceTypes) + foreach (var serviceType in MultiRegistrationServiceTypes) { AssertServiceCountEquals(services, serviceType.Key, serviceType.Value.Length + 1); @@ -222,14 +222,14 @@ namespace Microsoft.AspNetCore.Mvc var services = new ServiceCollection(); MvcCoreServiceCollectionExtensions.AddMvcCoreServices(services); - var multiRegistrationServiceTypes = MutliRegistrationServiceTypes; + var multiRegistrationServiceTypes = MultiRegistrationServiceTypes; return services .Where(sd => !multiRegistrationServiceTypes.Keys.Contains(sd.ServiceType)) .Select(sd => sd.ServiceType); } } - private Dictionary MutliRegistrationServiceTypes + private Dictionary MultiRegistrationServiceTypes { get { @@ -313,6 +313,13 @@ namespace Microsoft.AspNetCore.Mvc typeof(MvcEndpointDataSource), } }, + { + typeof(IStartupFilter), + new Type[] + { + typeof(MiddlewareFilterBuilderStartupFilter) + } + }, }; } } From dfbdb37979353c12274697b0228d6c113e00e483 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 13 Jul 2018 13:05:49 -0700 Subject: [PATCH 098/316] Refactorings for codefix (#8067) * Refactorings for codefix - Move some common code in SymbolApiResponseMetadataProvider - Don't run diagnostics for 1006 if we are unable to parse a return type --- .../ActualApiResponseMetadata.cs | 30 +++ .../ApiConventionAnalyzer.cs | 174 ++++------------- ...data.cs => DeclaredApiResponseMetadata.cs} | 4 +- .../SymbolApiResponseMetadataProvider.cs | 116 +++++++++++- .../ApiConventionAnalyzerIntegrationTest.cs | 37 +--- .../ApiConventionAnalyzerTest.cs | 175 ------------------ .../SymbolApiResponseMetadataProviderTest.cs | 125 ++++++++++++- ...ribute_DoesNotDocumentSuccessStatusCode.cs | 20 ++ .../ApiConventionAnalyzerTestFile.cs | 32 +--- .../GetDefaultStatusCodeTest.cs | 12 ++ ...etadata_IfReturnedTypeIsNotActionResult.cs | 12 ++ ...efaultStatusCodeAttributeOnActionResult.cs | 10 + 12 files changed, 361 insertions(+), 386 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs rename src/Microsoft.AspNetCore.Mvc.Analyzers/{ApiResponseMetadata.cs => DeclaredApiResponseMetadata.cs} (75%) create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs new file mode 100644 index 0000000000..3772f290e7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs @@ -0,0 +1,30 @@ +// 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.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal readonly struct ActualApiResponseMetadata + { + private readonly int? _statusCode; + + public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement) + { + ReturnStatement = returnStatement; + _statusCode = null; + } + + public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement, int statusCode) + { + ReturnStatement = returnStatement; + _statusCode = statusCode; + } + + public ReturnStatementSyntax ReturnStatement { get; } + + public int StatusCode => _statusCode.Value; + + public bool IsDefaultResponse => _statusCode == null; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs index 4efc083a19..57ce1b1e26 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; @@ -15,8 +14,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ApiConventionAnalyzer : DiagnosticAnalyzer { - private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; - public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, @@ -44,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { compilationStartAnalysisContext.RegisterSyntaxNodeAction(syntaxNodeContext => { + var cancellationToken = syntaxNodeContext.CancellationToken; var methodSyntax = (MethodDeclarationSyntax)syntaxNodeContext.Node; var semanticModel = syntaxNodeContext.SemanticModel; var method = semanticModel.GetDeclaredSymbol(methodSyntax, syntaxNodeContext.CancellationToken); @@ -54,34 +52,47 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } var conventionAttributes = GetConventionTypeAttributes(symbolCache, method); - var expectedResponseMetadata = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, conventionAttributes); - var actualResponseMetadata = new HashSet(); - - var context = new ApiConventionContext( - symbolCache, - syntaxNodeContext, - expectedResponseMetadata, - actualResponseMetadata); + var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, conventionAttributes); + var hasUnreadableStatusCodes = SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata); var hasUndocumentedStatusCodes = false; - foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) + foreach (var item in actualResponseMetadata) { - hasUndocumentedStatusCodes |= VisitReturnStatementSyntax(context, returnStatementSyntax); + var location = item.ReturnStatement.GetLocation(); + + if (item.IsDefaultResponse) + { + if (!(HasStatusCode(declaredResponseMetadata, 200) || HasStatusCode(declaredResponseMetadata, 201))) + { + hasUndocumentedStatusCodes = true; + syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, + location)); + } + } + else if (!HasStatusCode(declaredResponseMetadata, item.StatusCode)) + { + hasUndocumentedStatusCodes = true; + syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( + DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, + location, + item.StatusCode)); + } } - if (hasUndocumentedStatusCodes) + if (hasUndocumentedStatusCodes || hasUnreadableStatusCodes) { // If we produced analyzer warnings about undocumented status codes, don't attempt to determine // if there are documented status codes that are missing from the method body. return; } - for (var i = 0; i < expectedResponseMetadata.Count; i++) + for (var i = 0; i < declaredResponseMetadata.Count; i++) { - var expectedStatusCode = expectedResponseMetadata[i].StatusCode; - if (!actualResponseMetadata.Contains(expectedStatusCode)) + var expectedStatusCode = declaredResponseMetadata[i].StatusCode; + if (!HasStatusCode(actualResponseMetadata, expectedStatusCode)) { - context.SyntaxNodeContext.ReportDiagnostic(Diagnostic.Create( + syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, methodSyntax.Identifier.GetLocation(), expectedStatusCode)); @@ -102,86 +113,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return attributes; } - // Returns true if the return statement returns an undocumented status code. - private static bool VisitReturnStatementSyntax( - in ApiConventionContext context, - ReturnStatementSyntax returnStatementSyntax) - { - var returnExpression = returnStatementSyntax.Expression; - if (returnExpression.IsMissing) - { - return false; - } - - var syntaxNodeContext = context.SyntaxNodeContext; - - var typeInfo = syntaxNodeContext.SemanticModel.GetTypeInfo(returnExpression, syntaxNodeContext.CancellationToken); - if (typeInfo.Type.TypeKind == TypeKind.Error) - { - return false; - } - - var location = returnStatementSyntax.GetLocation(); - var diagnostic = InspectReturnExpression(context, typeInfo.Type, location); - if (diagnostic != null) - { - context.SyntaxNodeContext.ReportDiagnostic(diagnostic); - return true; - } - - return false; - } - - internal static Diagnostic InspectReturnExpression(in ApiConventionContext context, ITypeSymbol type, Location location) - { - var defaultStatusCodeAttribute = type - .GetAttributes(context.SymbolCache.DefaultStatusCodeAttribute, inherit: true) - .FirstOrDefault(); - - if (defaultStatusCodeAttribute != null) - { - var statusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); - if (statusCode == null) - { - // Unable to read the status code. Treat this as valid. - return null; - } - - context.ActualResponseMetadata.Add(statusCode.Value); - if (!HasStatusCode(context.ExpectedResponseMetadata, statusCode.Value)) - { - return Diagnostic.Create( - DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, - location, - statusCode); - } - } - else if (!context.SymbolCache.IActionResult.IsAssignableFrom(type)) - { - if (!HasStatusCode(context.ExpectedResponseMetadata, 200) && !HasStatusCode(context.ExpectedResponseMetadata, 201)) - { - return Diagnostic.Create( - DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, - location); - } - } - - return null; - } - - internal static int? GetDefaultStatusCode(AttributeData attribute) - { - if (attribute != null && - attribute.ConstructorArguments.Length == 1 && - attribute.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && - attribute.ConstructorArguments[0].Value is int statusCode) - { - return statusCode; - } - - return null; - } - internal static bool ShouldEvaluateMethod(ApiControllerSymbolCache symbolCache, IMethodSymbol method) { if (method == null) @@ -212,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return true; } - internal static bool HasStatusCode(IList declaredApiResponseMetadata, int statusCode) + internal static bool HasStatusCode(IList declaredApiResponseMetadata, int statusCode) { if (declaredApiResponseMetadata.Count == 0) { @@ -231,47 +162,22 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } - private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) + internal static bool HasStatusCode(IList actualResponseMetadata, int statusCode) { - return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && - !syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) && - !syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) && - !syntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression); - } - - - internal readonly struct ApiConventionContext - { - public ApiConventionContext( - ApiControllerSymbolCache symbolCache, - SyntaxNodeAnalysisContext syntaxNodeContext, - IList expectedResponseMetadata, - HashSet actualResponseMetadata, - Action reportDiagnostic = null) + for (var i = 0; i < actualResponseMetadata.Count; i++) { - SymbolCache = symbolCache; - SyntaxNodeContext = syntaxNodeContext; - ExpectedResponseMetadata = expectedResponseMetadata; - ActualResponseMetadata = actualResponseMetadata; - ReportDiagnosticAction = reportDiagnostic; - } - - public ApiControllerSymbolCache SymbolCache { get; } - public SyntaxNodeAnalysisContext SyntaxNodeContext { get; } - public IList ExpectedResponseMetadata { get; } - public HashSet ActualResponseMetadata { get; } - private Action ReportDiagnosticAction { get; } - - public void ReportDiagnostic(Diagnostic diagnostic) - { - if (ReportDiagnosticAction != null) + if (actualResponseMetadata[i].IsDefaultResponse) { - ReportDiagnosticAction(diagnostic); + return statusCode == 200 || statusCode == 201; } - SyntaxNodeContext.ReportDiagnostic(diagnostic); + else if(actualResponseMetadata[i].StatusCode == statusCode) + { + return true; + } } - } + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs similarity index 75% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs rename to src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs index 29ecd0ca27..75aa335209 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs @@ -5,9 +5,9 @@ using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Analyzers { - internal readonly struct ApiResponseMetadata + internal readonly struct DeclaredApiResponseMetadata { - public ApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) + public DeclaredApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) { StatusCode = statusCode; Attribute = attributeData; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs index c5c1367d43..9fbe1916ff 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -4,16 +4,20 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Mvc.Analyzers { - internal class SymbolApiResponseMetadataProvider + internal static class SymbolApiResponseMetadataProvider { private const string StatusCodeProperty = "StatusCode"; private const string StatusCodeConstructorParameter = "statusCode"; + private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; - internal static IList GetResponseMetadata( + internal static IList GetDeclaredResponseMetadata( ApiControllerSymbolCache symbolCache, IMethodSymbol method, IReadOnlyList conventionTypeAttributes) @@ -28,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return metadataItems; } - private static IList GetResponseMetadataFromConventions( + private static IList GetResponseMetadataFromConventions( ApiControllerSymbolCache symbolCache, IMethodSymbol method, IReadOnlyList attributes) @@ -58,17 +62,17 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } } - return Array.Empty(); + return Array.Empty(); } - private static IList GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) + private static IList GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) { - var metadataItems = new List(); + var metadataItems = new List(); var responseMetadataAttributes = methodSymbol.GetAttributes(symbolCache.ProducesResponseTypeAttribute, inherit: true); foreach (var attribute in responseMetadataAttributes) { var statusCode = GetStatusCode(attribute); - var metadata = new ApiResponseMetadata(statusCode, attribute, convention: null); + var metadata = new DeclaredApiResponseMetadata(statusCode, attribute, convention: null); metadataItems.Add(metadata); } @@ -119,5 +123,103 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return DefaultStatusCode; } + + internal static bool TryGetActualResponseMetadata( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + MethodDeclarationSyntax methodSyntax, + CancellationToken cancellationToken, + out IList actualResponseMetadata) + { + actualResponseMetadata = new List(); + + var hasUnreadableReturnStatements = false; + + foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) + { + var responseMetadata = InspectReturnStatementSyntax( + symbolCache, + semanticModel, + returnStatementSyntax, + cancellationToken); + + if (responseMetadata != null) + { + actualResponseMetadata.Add(responseMetadata.Value); + } + else + { + hasUnreadableReturnStatements = true; + } + } + + return hasUnreadableReturnStatements; + } + + internal static ActualApiResponseMetadata? InspectReturnStatementSyntax( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + ReturnStatementSyntax returnStatementSyntax, + CancellationToken cancellationToken) + { + var returnExpression = returnStatementSyntax.Expression; + if (returnExpression.IsMissing) + { + return null; + } + + var typeInfo = semanticModel.GetTypeInfo(returnExpression, cancellationToken); + if (typeInfo.Type.TypeKind == TypeKind.Error) + { + return null; + } + + var statementReturnType = typeInfo.Type; + + var defaultStatusCodeAttribute = statementReturnType + .GetAttributes(symbolCache.DefaultStatusCodeAttribute, inherit: true) + .FirstOrDefault(); + + if (defaultStatusCodeAttribute != null) + { + var statusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); + if (statusCode == null) + { + // Unable to read the status code even though the attribute exists. + return null; + } + + return new ActualApiResponseMetadata(returnStatementSyntax, statusCode.Value); + } + else if (!symbolCache.IActionResult.IsAssignableFrom(statementReturnType)) + { + // Return expression does not have a DefaultStatusCodeAttribute and it is not + // an instance of IActionResult. Must be returning the "model". + return new ActualApiResponseMetadata(returnStatementSyntax); + } + + return null; + } + + private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) + { + return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && + !syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression); + } + + internal static int? GetDefaultStatusCode(AttributeData attribute) + { + if (attribute != null && + attribute.ConstructorArguments.Length == 1 && + attribute.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attribute.ConstructorArguments[0].Value is int statusCode) + { + return statusCode; + } + + return null; + } } } \ No newline at end of file diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index 75307db01f..0fdd22a66a 100644 --- a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiConventionAnalyzerIntegrationTest { - private MvcDiagnosticAnalyzerRunner Executor { get; } = new MvcDiagnosticAnalyzerRunner(new ApiConventionAnalyzer()); + private MvcDiagnosticAnalyzerRunner Executor { get; } = new ApiCoventionWith1006DiagnosticEnabledRunner(); [Fact] public Task NoDiagnosticsAreReturned_ForNonApiController() @@ -77,11 +77,15 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [Fact] public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode() - => RunTestFor1006(400); + => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 400); [Fact] public Task DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode() - => RunTestFor1006(404); + => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 404); + + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode() + => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 200); private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") { @@ -122,30 +126,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), args), diagnostic.GetMessage()); }); } - - private async Task RunTestFor1006(int statusCode, [CallerMemberName] string testMethod = "") - { - // Arrange - var descriptor = DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode; - var testSource = MvcTestSource.Read(GetType().Name, testMethod); - var expectedLocation = testSource.DefaultMarkerLocation; - var executor = new ApiCoventionWith1006DiagnosticEnabledRunner(); - - // Act - var result = await executor.GetDiagnosticsAsync(testSource.Source); - - // Assert - Assert.Collection( - result, - diagnostic => - { - Assert.Equal(descriptor.Id, diagnostic.Id); - Assert.Same(descriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), new[] { statusCode.ToString() }), diagnostic.GetMessage()); - }); - } - private class ApiCoventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner { public ApiCoventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer()) @@ -155,6 +135,9 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers protected override CompilationOptions ConfigureCompilationOptions(CompilationOptions options) { var compilationOptions = base.ConfigureCompilationOptions(options); + + // 10006 is disabled by default. Explicitly enable it so we can correctly validate no diagnostics + // are returned scenarios. var specificDiagnosticOptions = compilationOptions.SpecificDiagnosticOptions.Add( DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode.Id, ReportDiagnostic.Info); diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs index 65283bc5f8..b1e7578557 100644 --- a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs +++ b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs @@ -1,181 +1,17 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -using static Microsoft.AspNetCore.Mvc.Analyzers.ApiConventionAnalyzer; namespace Microsoft.AspNetCore.Mvc.Analyzers { public class ApiConventionAnalyzerTest { - [Fact] - public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingStatusCodeConstants() - { - // Arrange - var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingStatusCodesConstants).FullName).GetAttributes()[0]; - - // Act - var actual = ApiConventionAnalyzer.GetDefaultStatusCode(attribute); - - // Assert - Assert.Equal(412, actual); - } - - [Fact] - public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingHttpStatusCast() - { - // Arrange - var compilation = await GetCompilation(); - var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingHttpStatusCodeCast).FullName).GetAttributes()[0]; - - // Act - var actual = ApiConventionAnalyzer.GetDefaultStatusCode(attribute); - - // Assert - Assert.Equal(302, actual); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsNull_ForReturnTypeIf200StatusCodeIsDeclared() - { - // Arrange - var compilation = await GetCompilation(); - - var returnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); - var context = GetContext(compilation, new[] { 200 }); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, returnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsNull_ForReturnTypeIf201StatusCodeIsDeclared() - { - // Arrange - var compilation = await GetCompilation(); - - var returnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); - var context = GetContext(compilation, new[] { 201 }); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, returnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsNull_ForDerivedReturnTypeIf200StatusCodeIsDeclared() - { - // Arrange - var compilation = await GetCompilation(); - - var declaredReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); - var context = GetContext(compilation, new[] { 201 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerDerivedModel).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsDiagnostic_If200IsNotDocumented() - { - // Arrange - var compilation = await GetCompilation(); - - var context = GetContext(compilation, new[] { 404 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerDerivedModel).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.NotNull(diagnostic); - Assert.Same(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, diagnostic.Descriptor); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsDiagnostic_IfReturnTypeIsActionResultReturningUndocumentedStatusCode() - { - // Arrange - var compilation = await GetCompilation(); - - var declaredReturnType = compilation.GetTypeByMetadataName(typeof(ApiConventionAnalyzerBaseModel).FullName); - var context = GetContext(compilation, new[] { 200, 404 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(BadRequestObjectResult).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.NotNull(diagnostic); - Assert.Same(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, diagnostic.Descriptor); - } - - [Fact] - public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfReturnTypeDoesNotHaveStatusCodeAttribute() - { - // Arrange - var compilation = await GetCompilation(); - - var context = GetContext(compilation, new[] { 200, 404 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(EmptyResult).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - - [Fact] - public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfDeclaredAndActualReturnTypeAreIActionResultInstances() - { - // Arrange - var compilation = await GetCompilation(); - - var declaredReturnType = compilation.GetTypeByMetadataName(typeof(IActionResult).FullName); - var context = GetContext(compilation, new[] { 404 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(EmptyResult).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - - [Fact] - public async Task InspectReturnExpression_DoesNotReturnDiagnostic_IfDeclaredAndActualReturnTypeAreIActionResult() - { - // Arrange - var compilation = await GetCompilation(); - - var context = GetContext(compilation, new[] { 404 }); - var actualReturnType = compilation.GetTypeByMetadataName(typeof(IActionResult).FullName); - - // Act - var diagnostic = ApiConventionAnalyzer.InspectReturnExpression(context, actualReturnType, Location.None); - - // Assert - Assert.Null(diagnostic); - } - [Fact] public async Task ShouldEvaluateMethod_ReturnsFalse_IfMethodReturnTypeIsInvalid() { @@ -275,17 +111,6 @@ namespace TestNamespace Assert.True(result); } - private static ApiConventionContext GetContext(Compilation compilation, int[] expectedStatusCodes) - { - var symbolCache = new ApiControllerSymbolCache(compilation); - var context = new ApiConventionContext( - symbolCache, - default, - expectedStatusCodes.Select(s => new ApiResponseMetadata(s, null, null)).ToArray(), - new HashSet()); - return context; - } - private Task GetCompilation() { var testSource = MvcTestSource.Read(GetType().Name, "ApiConventionAnalyzerTestFile"); diff --git a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index c31846a88d..32f764fb67 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -2,11 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using System.Runtime.CompilerServices; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers @@ -25,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -41,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -57,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -73,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -96,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -119,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -142,7 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -165,7 +169,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -188,7 +192,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Empty(result); @@ -219,7 +223,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); // Assert Assert.Collection( @@ -299,6 +303,107 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers Assert.Equal(expected, statusCode); } + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingStatusCodeConstants() + { + // Arrange + var compilation = await GetCompilation("GetDefaultStatusCodeTest"); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingStatusCodesConstants).FullName).GetAttributes()[0]; + + // Act + var actual = SymbolApiResponseMetadataProvider.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(412, actual); + } + + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingHttpStatusCast() + { + // Arrange + var compilation = await GetCompilation("GetDefaultStatusCodeTest"); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingHttpStatusCodeCast).FullName).GetAttributes()[0]; + + // Act + var actual = SymbolApiResponseMetadataProvider.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(302, actual); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound() + { + // Arrange & Act + var source = @" + using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound : ControllerBase + { + public IActionResult Get(int id) + { + return new DoesNotExist(id); + } + } +}"; + var actualResponseMetadata = await RunInspectReturnStatementSyntax(source, nameof(InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound)); + + // Assert + Assert.Null(actualResponseMetadata); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(401, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.True(actualResponseMetadata.Value.IsDefaultResponse); + } + + private async Task RunInspectReturnStatementSyntax([CallerMemberName]string test = null) + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, test); + return await RunInspectReturnStatementSyntax(testSource.Source, test); + } + + private async Task RunInspectReturnStatementSyntax(string source, string test) + { + var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); + var compilation = await project.GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}"); + var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree; + + var method = (IMethodSymbol)returnType.GetMembers().First(); + var methodSyntax = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var returnStatement = methodSyntax.DescendantNodes().OfType().First(); + + return SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( + symbolCache, + compilation.GetSemanticModel(syntaxTree), + returnStatement, + CancellationToken.None); + } + private Task GetResponseMetadataCompilation() => GetCompilation("GetResponseMetadataTests"); private Task GetCompilation(string test) diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs new file mode 100644 index 0000000000..bdbc7bf659 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs @@ -0,0 +1,20 @@ +using System; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode : ControllerBase + { + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public ActionResult /*MM*/Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + throw new NotImplementedException(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs index c4d6a646a9..397c9b86b1 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs @@ -1,37 +1,7 @@ -using System.Collections.Generic; -using System.Net; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Mvc.RazorPages; namespace Microsoft.AspNetCore.Mvc.Analyzers { - public class UnwrapMethodReturnType - { - public ApiConventionAnalyzerBaseModel ReturnsBaseModel() => null; - - public ActionResult ReturnsActionResultOfBaseModel() => null; - - public Task> ReturnsTaskOfActionResultOfBaseModel() => null; - - public ValueTask> ReturnsValueTaskOfActionResultOfBaseModel() => default(ValueTask>); - - public ActionResult> ReturnsActionResultOfIEnumerableOfBaseModel() => null; - - public IEnumerable ReturnsIEnumerableOfBaseModel() => null; - } - - [DefaultStatusCode(StatusCodes.Status412PreconditionFailed)] - public class TestActionResultUsingStatusCodesConstants { } - - [DefaultStatusCode((int)HttpStatusCode.Found)] - public class TestActionResultUsingHttpStatusCodeCast { } - - public class ApiConventionAnalyzerBaseModel { } - - public class ApiConventionAnalyzerDerivedModel : ApiConventionAnalyzerBaseModel { } - public class ApiConventionAnalyzerTest_IndexModel : PageModel { public IActionResult OnGet() => null; diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs new file mode 100644 index 0000000000..6d9412a840 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs @@ -0,0 +1,12 @@ +using System.Net; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [DefaultStatusCode(StatusCodes.Status412PreconditionFailed)] + public class TestActionResultUsingStatusCodesConstants { } + + [DefaultStatusCode((int)HttpStatusCode.Redirect)] + public class TestActionResultUsingHttpStatusCodeCast { } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs new file mode 100644 index 0000000000..ce2dbbb39b --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs @@ -0,0 +1,12 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult : ControllerBase + { + public object Get() + { + return new InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResultModel(); + } + } + + public class InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResultModel { } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs new file mode 100644 index 0000000000..4d5d0d9849 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult : ControllerBase + { + public IActionResult Get() + { + return Unauthorized(); + } + } +} From f2608c2ff4084eb617a63ca6056ca1955edafd86 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 12 Jul 2018 11:21:21 -0700 Subject: [PATCH 099/316] Do not suppress `ModelValidationState.Invalid` entries - #7992, #7963 --- .../Validation/ValidationVisitor.cs | 5 +- .../Internal/DefaultObjectValidatorTests.cs | 63 ++++++++ .../ModelBinding/ParameterBinderTest.cs | 148 +++++++++++++++--- 3 files changed, 193 insertions(+), 23 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index f8c4c02dd7..ecf44973cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -296,7 +296,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation var entries = ModelState.FindKeysWithPrefix(key); foreach (var entry in entries) { - entry.Value.ValidationState = ModelValidationState.Skipped; + if (entry.Value.ValidationState != ModelValidationState.Invalid) + { + entry.Value.ValidationState = ModelValidationState.Skipped; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs index 5371633029..a9ee38cfc6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs @@ -133,6 +133,27 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Empty(entry.Errors); } + // More like how product code does suppressions than Validate_SimpleType_SuppressValidation() + [Fact] + public void Validate_SimpleType_SuppressValidationWithNullKey() + { + // Arrange + var actionContext = new ActionContext(); + var modelState = actionContext.ModelState; + var validator = CreateValidator(); + var model = "test"; + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry { SuppressValidation = true } } + }; + + // Act + validator.Validate(actionContext, validationState, "parameter", model); + + // Assert + Assert.True(modelState.IsValid); + Assert.Empty(modelState); + } [Fact] public void Validate_ComplexValueType_Valid() @@ -1195,6 +1216,48 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Empty(entry.Value.Errors); } + // Regression test for aspnet/Mvc#7992 + [Fact] + public void Validate_SuppressValidation_AfterHasReachedMaxErrors_Invalid() + { + // Arrange + var actionContext = new ActionContext(); + var modelState = actionContext.ModelState; + modelState.MaxAllowedErrors = 2; + modelState.AddModelError(key: "one", errorMessage: "1"); + modelState.AddModelError(key: "two", errorMessage: "2"); + + var validator = CreateValidator(); + var model = (object)23; // Box ASAP + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry { SuppressValidation = true } } + }; + + // Act + validator.Validate(actionContext, validationState, prefix: string.Empty, model); + + // Assert + Assert.False(modelState.IsValid); + Assert.True(modelState.HasReachedMaxErrors); + Assert.Collection( + modelState, + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState); + var error = Assert.Single(kvp.Value.Errors); + Assert.IsType(error.Exception); + }, + kvp => + { + Assert.Equal("one", kvp.Key); + Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("1", error.ErrorMessage); + }); + } + private static DefaultObjectValidator CreateValidator(Type excludedType) { var excludeFilters = new List(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index 7a605c4640..8c0ea54528 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.DataAnnotations; @@ -706,6 +707,131 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding }); } + // Regression test 1 for aspnet/Mvc#7963. ModelState should never be valid. + [Fact] + public async Task BindModelAsync_ForOverlappingParametersWithSuppressions_InValid_WithValidSecondParameter() + { + // Arrange + var parameterDescriptor = new ParameterDescriptor + { + Name = "patchDocument", + ParameterType = typeof(IJsonPatchDocument), + }; + + var actionContext = GetControllerContext(); + var modelState = actionContext.ModelState; + + // First ModelState key is not empty to match SimpleTypeModelBinder. + modelState.SetModelValue("id", "notAGuid", "notAGuid"); + modelState.AddModelError("id", "This is not valid."); + + var modelMetadataProvider = new TestModelMetadataProvider(); + modelMetadataProvider.ForType().ValidationDetails(v => v.ValidateChildren = false); + var modelMetadata = modelMetadataProvider.GetMetadataForType(typeof(IJsonPatchDocument)); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + // BodyModelBinder does not update ModelState in success case. + var modelBindingResult = ModelBindingResult.Success(new JsonPatchDocument()); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + new SimpleValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.False(modelState.IsValid); + Assert.Collection( + modelState, + kvp => + { + Assert.Equal("id", kvp.Key); + Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("This is not valid.", error.ErrorMessage); + }); + } + + // Regression test 2 for aspnet/Mvc#7963. ModelState should never be valid. + [Fact] + public async Task BindModelAsync_ForOverlappingParametersWithSuppressions_InValid_WithInValidSecondParameter() + { + // Arrange + var parameterDescriptor = new ParameterDescriptor + { + Name = "patchDocument", + ParameterType = typeof(IJsonPatchDocument), + }; + + var actionContext = GetControllerContext(); + var modelState = actionContext.ModelState; + + // First ModelState key is not empty to match SimpleTypeModelBinder. + modelState.SetModelValue("id", "notAGuid", "notAGuid"); + modelState.AddModelError("id", "This is not valid."); + + // Second ModelState key is empty to match BodyModelBinder. + modelState.AddModelError(string.Empty, "This is also not valid."); + + var modelMetadataProvider = new TestModelMetadataProvider(); + modelMetadataProvider.ForType().ValidationDetails(v => v.ValidateChildren = false); + var modelMetadata = modelMetadataProvider.GetMetadataForType(typeof(IJsonPatchDocument)); + + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + var modelBindingResult = ModelBindingResult.Failed(); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + new SimpleValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.False(result.IsModelSet); + Assert.False(modelState.IsValid); + Assert.Collection( + modelState, + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("This is also not valid.", error.ErrorMessage); + }, + kvp => + { + Assert.Equal("id", kvp.Key); + Assert.Equal(ModelValidationState.Invalid, kvp.Value.ValidationState); + var error = Assert.Single(kvp.Value.Errors); + Assert.Equal("This is not valid.", error.ErrorMessage); + }); + } + private static ControllerContext GetControllerContext() { var services = new ServiceCollection(); @@ -813,25 +939,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding return mockValueProvider.Object; } - private static IModelValidatorProvider CreateMockValidatorProvider(IModelValidator validator = null) - { - var mockValidator = new Mock(); - mockValidator - .Setup(o => o.CreateValidators( - It.IsAny())) - .Callback(context => - { - if (validator != null) - { - foreach (var result in context.Results) - { - result.Validator = validator; - } - } - }); - return mockValidator.Object; - } - private class Person : IEquatable, IEquatable { public string Name { get; set; } @@ -862,9 +969,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public string DerivedProperty { get; set; } } - [Required] - private Person PersonProperty { get; set; } - public abstract class FakeModelMetadata : ModelMetadata { public FakeModelMetadata() From 35fad0881b85591ca2002bacfc13a7e42755ef81 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 12 Jul 2018 15:23:42 -0700 Subject: [PATCH 100/316] Mark unused `ParameterBinder.BindModelAsync(...)` overloads as `[Obsolete]` - #7660 - also addresses part of #7317, only in `ComplexTypeModelBinderIntegrationTest` --- .../ModelBinding/ParameterBinder.cs | 20 +- .../Internal/DefaultPageArgumentBinder.cs | 2 + .../Internal/MiddlewareFilterTest.cs | 48 -- .../ModelBinding/ParameterBinderTest.cs | 8 +- .../ComplexTypeModelBinderIntegrationTest.cs | 667 +++++++++++++++--- .../ModelBindingTestHelper.cs | 5 + 6 files changed, 591 insertions(+), 159 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs index 51ed9c3034..0cf3693eb1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs @@ -101,28 +101,46 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding protected ILogger Logger { get; } /// - /// Initializes and binds a model specified by . + /// + /// This method overload is obsolete and will be removed in a future version. The recommended alternative is + /// . + /// + /// Initializes and binds a model specified by . /// /// The . /// The . /// The /// The result of model binding. + [Obsolete("This method overload is obsolete and will be removed in a future version. The recommended " + + "alternative is the overload that also takes " + nameof(IModelBinder) + ", " + nameof(ModelMetadata) + + " and " + nameof(Object) + " parameters.")] public Task BindModelAsync( ActionContext actionContext, IValueProvider valueProvider, ParameterDescriptor parameter) { +#pragma warning disable CS0618 // Type or member is obsolete return BindModelAsync(actionContext, valueProvider, parameter, value: null); +#pragma warning restore CS0618 // Type or member is obsolete } /// + /// + /// This method overload is obsolete and will be removed in a future version. The recommended alternative is + /// . + /// + /// /// Binds a model specified by using as the initial value. + /// /// /// The . /// The . /// The /// The initial model value. /// The result of model binding. + [Obsolete("This method overload is obsolete and will be removed in a future version. The recommended " + + "alternative is the overload that also takes " + nameof(IModelBinder) + " and " + nameof(ModelMetadata) + + " parameters.")] public virtual Task BindModelAsync( ActionContext actionContext, IValueProvider valueProvider, diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs index 7b3310bbdd..9fd638c10c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs @@ -30,7 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal ParameterType = type, }; +#pragma warning disable CS0618 // Type or member is obsolete return await _parameterBinder.BindModelAsync(pageContext, valueProvider, parameterDescriptor, value); +#pragma warning restore CS0618 // Type or member is obsolete } private static async Task GetCompositeValueProvider(PageContext pageContext) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs index e9c770e27a..e6c748784e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs @@ -16,7 +16,6 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; @@ -450,57 +449,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private class TestParameterBinder : ParameterBinder - { - private readonly IDictionary _actionParameters; - - public TestParameterBinder(IDictionary actionParameters) - : base( - new EmptyModelMetadataProvider(), - TestModelBinderFactory.CreateDefault(), - Mock.Of(), - Options.Create(new MvcOptions - { - AllowValidatingTopLevelNodes = true, - }), - NullLoggerFactory.Instance) - { - _actionParameters = actionParameters; - } - - public override Task BindModelAsync( - ActionContext actionContext, - IValueProvider valueProvider, - ParameterDescriptor parameter, - object value) - { - if (_actionParameters.TryGetValue(parameter.Name, out var result)) - { - return Task.FromResult(ModelBindingResult.Success(result)); - } - - return Task.FromResult(ModelBindingResult.Failed()); - } - - public Task BindArgumentsAsync( - ControllerContext controllerContext, - object controller, - IDictionary arguments) - { - foreach (var entry in _actionParameters) - { - arguments.Add(entry.Key, entry.Value); - } - - return Task.CompletedTask; - } - } - private sealed class TestController { } - private enum TestResourceFilterAction { ShortCircuit, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index 8c0ea54528..8b7ea899ed 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -62,7 +62,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [Theory] [MemberData(nameof(BindModelAsyncData))] - public async Task BindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixDoesNotMatch( + public async Task ObsoleteBindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixDoesNotMatch( BindingInfo parameterBindingInfo, string metadataBinderModelName, string parameterName, @@ -116,13 +116,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var controllerContext = GetControllerContext(); // Act & Assert +#pragma warning disable CS0618 // Type or member is obsolete await parameterBinder.BindModelAsync(controllerContext, new SimpleValueProvider(), parameterDescriptor); +#pragma warning restore CS0618 // Type or member is obsolete Assert.True(binderExecuted); } [Fact] - public async Task BindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixMatches() + public async Task ObsoleteBindModelAsync_PassesExpectedBindingInfoAndMetadata_IfPrefixMatches() { // Arrange var expectedModelName = "expectedName"; @@ -174,7 +176,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding var controllerContext = GetControllerContext(); // Act & Assert +#pragma warning disable CS0618 // Type or member is obsolete await argumentBinder.BindModelAsync(controllerContext, valueProvider, parameterDescriptor); +#pragma warning restore CS0618 // Type or member is obsolete Assert.True(binderExecuted); } diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs index e3f693f58a..0571b5fac1 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs @@ -50,7 +50,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -65,10 +64,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -92,7 +100,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithEmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -107,10 +114,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -150,11 +166,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests testContext.MvcOptions.AllowEmptyInputInBodyModelBinding = true; var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext.HttpContext.RequestServices); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -179,7 +203,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_PartialData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -194,10 +217,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -221,7 +253,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -236,10 +267,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -270,7 +310,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -285,10 +324,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -315,7 +363,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithEmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -329,10 +376,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -359,7 +415,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithByteArrayModelBinder_WithPrefix_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -373,10 +428,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -413,7 +477,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -428,10 +491,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -458,7 +530,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithEmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -473,10 +544,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -503,7 +583,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_NoBodyData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -519,10 +598,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -548,7 +636,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_PartialData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -563,10 +650,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -590,7 +686,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -605,10 +700,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -632,7 +736,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsArrayProperty_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -647,10 +750,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -680,7 +792,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsArrayProperty_EmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -694,10 +805,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -727,7 +847,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsArrayProperty_NoCollectionData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -741,10 +860,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -766,7 +894,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsArrayProperty_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -780,10 +907,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -808,7 +944,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsListProperty_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -823,10 +958,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -856,7 +1000,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsListProperty_EmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -870,10 +1013,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -903,7 +1055,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsListProperty_NoCollectionData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -917,10 +1068,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -942,7 +1102,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsListProperty_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -956,10 +1115,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -984,7 +1152,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -999,10 +1166,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1032,7 +1208,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_EmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1046,10 +1221,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1079,7 +1263,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_NoCollectionData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1093,10 +1276,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1118,7 +1310,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1132,10 +1323,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1181,7 +1381,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithIEnumerableComplexTypeValue_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "p", @@ -1201,10 +1400,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1280,7 +1488,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithArrayOfComplexTypeValue_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "p", @@ -1300,10 +1507,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1379,7 +1595,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsDictionaryProperty_WithIEnumerableOfKeyValuePair_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "p", @@ -1399,10 +1614,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1485,7 +1709,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1500,10 +1723,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1533,7 +1765,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_EmptyPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1547,10 +1778,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1580,7 +1820,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_NoCollectionData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1594,10 +1833,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1619,7 +1867,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_NoData() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1633,10 +1880,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1661,7 +1917,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task Foo_MutableObjectModelBinder_BindsKeyValuePairProperty_WithPrefix_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "p", @@ -1682,10 +1937,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1752,7 +2016,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsNestedPOCO_WithAllGreedyBoundProperties() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1767,10 +2030,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1801,7 +2073,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredComplexProperty_NoData_GetsErrors() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1812,10 +2083,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext(); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1848,7 +2128,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests name => $"Hurts when '{ name }' is not provided."); })); - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(metadataProvider); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1856,13 +2135,22 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }; // No Data - var testContext = ModelBindingTestHelper.GetTestContext(); + var testContext = ModelBindingTestHelper.GetTestContext(metadataProvider: metadataProvider); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1898,7 +2186,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithPartialData_GetsErrors() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1912,10 +2199,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1944,7 +2240,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithData_EmptyPrefix_GetsErrors() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -1958,10 +2253,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -1990,7 +2294,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithNestedRequiredProperty_WithData_CustomPrefix_GetsErrors() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2008,10 +2311,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2046,7 +2358,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredProperty_NoData_GetsErrors() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2060,10 +2371,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2086,7 +2406,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredProperty_NoData_CustomPrefix_GetsErros() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2104,10 +2423,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2130,7 +2458,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredProperty_WithData_EmptyPrefix_GetsBound() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2144,10 +2471,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2174,7 +2510,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_NoData_GetsErros() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2188,10 +2523,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2214,7 +2558,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_NoData_CustomPrefix_GetsErros() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2232,10 +2575,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2258,24 +2610,31 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_WithRequiredCollectionProperty_WithData_EmptyPrefix_GetsBound() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", ParameterType = typeof(Order13), }; - // No Data var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = new QueryString("?OrderIds[0]=123"); }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2303,7 +2662,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsPOCO_TypeConvertedPropertyNonConvertableValue_GetsError() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2317,10 +2675,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2349,7 +2716,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsPOCO_TypeConvertedPropertyWithEmptyValue_Error() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2363,10 +2729,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2408,7 +2783,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task ModelNameOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo) { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor { Name = "parameter-name", @@ -2418,11 +2792,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext( request => request.QueryString = new QueryString("?HomeAddress.Street=someStreet")); + var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2448,7 +2832,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task ModelNameOnParameterType_WithData_Succeeds(BindingInfo bindingInfo) { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor { Name = "parameter-name", @@ -2458,11 +2841,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext( request => request.QueryString = new QueryString("?HomeAddress.Street=someStreet")); + var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2506,7 +2899,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task BindAttributeOnPropertyType_WithData_Succeeds(BindingInfo bindingInfo) { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor { Name = "parameter-name", @@ -2517,11 +2909,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext( request => request.QueryString = new QueryString( "?Address.Number=23&Address.Street=someStreet&Address.City=Redmond&Address.State=WA")); + var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2552,7 +2954,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task BindAttributeOnParameterType_WithData_Succeeds(BindingInfo bindingInfo) { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor { Name = "parameter-name", @@ -2562,11 +2963,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext( request => request.QueryString = new QueryString("?Number=23&Street=someStreet&City=Redmond&State=WA")); + var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2601,7 +3012,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task ComplexTypeModelBinder_BindsSettableProperties(string queryString) { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2614,10 +3024,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests request.QueryString = new QueryString(queryString); SetJsonBodyContent(request, AddressBodyContent); }); + + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2648,7 +3068,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public async Task MutableObjectModelBinder_BindsKeyValuePairProperty_HavingFromHeaderProperty_Success() { // Arrange - var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); var parameter = new ParameterDescriptor() { Name = "parameter", @@ -2663,10 +3082,19 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); var modelState = testContext.ModelState; + var metadata = GetMetadata(testContext, parameter); + var modelBinder = GetModelBinder(testContext, parameter, metadata); var valueProvider = await CompositeValueProvider.CreateAsync(testContext); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(testContext); // Act - var modelBindingResult = await parameterBinder.BindModelAsync(testContext, valueProvider, parameter); + var modelBindingResult = await parameterBinder.BindModelAsync( + testContext, + modelBinder, + valueProvider, + parameter, + metadata, + value: null); // Assert Assert.True(modelBindingResult.IsModelSet); @@ -2724,5 +3152,28 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Headers = request.Headers }); } + + private ModelMetadata GetMetadata(ModelBindingTestContext context, ParameterDescriptor parameter) + { + return context.MetadataProvider.GetMetadataForType(parameter.ParameterType); + } + + private IModelBinder GetModelBinder( + ModelBindingTestContext context, + ParameterDescriptor parameter, + ModelMetadata metadata) + { + var factory = ModelBindingTestHelper.GetModelBinderFactory( + context.MetadataProvider, + context.HttpContext.RequestServices); + var factoryContext = new ModelBinderFactoryContext + { + BindingInfo = parameter.BindingInfo, + CacheToken = parameter, + Metadata = metadata, + }; + + return factory.CreateBinder(factoryContext); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs index afd6b0ca40..b3686104a8 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -64,6 +64,11 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests } } + public static ParameterBinder GetParameterBinder(ModelBindingTestContext testContext) + { + return GetParameterBinder(testContext.HttpContext.RequestServices); + } + public static ParameterBinder GetParameterBinder(IServiceProvider serviceProvider) { var metadataProvider = serviceProvider.GetRequiredService(); From 4f4243285322429bdb3f99b356b663c1f44efd2b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 15 Jul 2018 12:22:00 -0700 Subject: [PATCH 101/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 158 +++++++++++++++++++-------------------- 1 file changed, 79 insertions(+), 79 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3f80a4948b..3577022de9 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,95 +16,95 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 2.2.0-preview1-17099 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 1.7.0 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-26618-02 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 - 2.0.7 - 2.1.1 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.1.0 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 + 2.0.9 + 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34640 - 2.2.0-preview1-34640 + 2.2.0-preview1-34694 + 2.2.0-preview1-34694 15.6.1 4.7.49 2.0.3 1.0.1 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 4.6.0-preview1-26617-01 - 0.8.0 + 4.5.0 + 4.5.0 + 4.5.1 + 0.9.0 2.3.1 2.4.0-rc.1.build4038 From 9d951325b2f1ea87e5beeb35abb32e5829f3ec7b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 9 Jul 2018 17:02:52 -0700 Subject: [PATCH 102/316] Re-organize shared src packages so we can do true IVT between src assemblies --- ...crosoft.AspNetCore.Mvc.Abstractions.csproj | 9 +++--- .../Properties/AssemblyInfo.cs | 30 +++++++++++++++++ ...icrosoft.AspNetCore.Mvc.ApiExplorer.csproj | 4 --- .../ConsumesAttribute.cs | 2 +- .../Internal/ActionSelector.cs | 2 +- .../Internal/AttributeRoute.cs | 2 +- .../ControllerActionDescriptorBuilder.cs | 2 +- .../Internal/ControllerActionInvoker.cs | 2 +- .../Internal/MvcAttributeRouteHandler.cs | 2 +- .../Microsoft.AspNetCore.Mvc.Core.csproj | 25 ++++++++------- .../Properties/AssemblyInfo.cs | 32 +++++++++++++++++-- ...soft.AspNetCore.Mvc.DataAnnotations.csproj | 2 -- ...osoft.AspNetCore.Mvc.Formatters.Xml.csproj | 3 -- ...crosoft.AspNetCore.Mvc.Localization.csproj | 1 - .../Microsoft.AspNetCore.Mvc.Razor.csproj | 4 --- .../Properties/AssemblyInfo.cs | 2 ++ .../MvcRazorPagesMvcBuilderExtensions.cs | 1 + .../MvcRazorPagesMvcCoreBuilderExtensions.cs | 1 + ...Microsoft.AspNetCore.Mvc.RazorPages.csproj | 10 ------ ...Microsoft.AspNetCore.Mvc.TagHelpers.csproj | 2 -- ...crosoft.AspNetCore.Mvc.ViewFeatures.csproj | 8 ----- .../Internal/TestResources.cs | 2 +- .../Rendering/TestResources.cs | 2 +- 23 files changed, 91 insertions(+), 59 deletions(-) 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/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/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.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/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.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 { From b62499e02c274db162151556318b0a5fae85360e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 12 Jul 2018 17:27:32 -0700 Subject: [PATCH 103/316] Ensure PageContext.ViewData and ViewContext.ViewData are the same instance Fixes #7675 --- .../IModelTypeProvider.cs | 9 +++ .../RazorPageActivator.cs | 65 ++++++++++++++-- .../RazorView.cs | 4 + .../Infrastructure/PageResultExecutor.cs | 20 ++++- .../Infrastructure/RazorPageAdapter.cs | 12 ++- .../RazorPagesWithBasePathTest.cs | 16 ++++ .../RazorPageActivatorTest.cs | 78 +++++++++++++++++++ .../RazorViewTest.cs | 43 ++++++++++ .../ViewData/ViewDataSetInViewStart/Index.cs | 11 +++ .../ViewDataSetInViewStart/Index.cshtml | 7 ++ .../ViewDataSetInViewStart/_Layout.cshtml | 4 + .../ViewDataSetInViewStart/_ViewStart.cshtml | 4 + 12 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs create mode 100644 test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml 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/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/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/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.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/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 From 42218d5fb553337d84105e146f3ad771ab309089 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 17 Jul 2018 16:37:45 +1200 Subject: [PATCH 104/316] Versioning with endpoint constraint (#8098) --- .../Internal/MvcEndpointDataSource.cs | 44 +- .../VersioningDispatchingTests.cs | 13 + .../VersioningTests.cs | 553 +---------------- .../VersioningTestsBase.cs | 568 ++++++++++++++++++ test/WebSites/VersioningWebSite/Program.cs | 26 + test/WebSites/VersioningWebSite/Startup.cs | 18 +- .../StartupWithDispatching.cs | 37 ++ .../VersioningWebSite/VersionAttribute.cs | 10 +- .../VersionDeleteAttribute.cs | 2 +- .../VersioningWebSite/VersionGetAttribute.cs | 2 +- .../VersioningWebSite/VersionPostAttribute.cs | 2 +- .../VersioningWebSite/VersionPutAttribute.cs | 2 +- .../VersionRangeValidator.cs | 15 +- ...rsionRoute.cs => VersionRouteAttribute.cs} | 20 +- 14 files changed, 715 insertions(+), 597 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs create mode 100644 test/WebSites/VersioningWebSite/Program.cs create mode 100644 test/WebSites/VersioningWebSite/StartupWithDispatching.cs rename test/WebSites/VersioningWebSite/{VersionRoute.cs => VersionRouteAttribute.cs} (84%) 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/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/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 From 0caacb8217a2a16dfa829b5dead4f7a5c0bf0dc0 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 17 Jul 2018 05:08:06 -0700 Subject: [PATCH 105/316] React to LinkGenerator api changes --- .../Routing/DispatcherUrlHelper.cs | 10 ++++++---- .../Routing/UrlHelperFactory.cs | 2 +- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs index ea56fb5b6a..8b0efc4c38 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs @@ -9,13 +9,13 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing { /// - /// An implementation of that uses to build URLs + /// An implementation of that uses to build URLs /// for ASP.NET MVC within an application. /// internal class DispatcherUrlHelper : UrlHelperBase { private readonly ILogger _logger; - private readonly ILinkGenerator _linkGenerator; + private readonly LinkGenerator _linkGenerator; private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// @@ -26,12 +26,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The which finds endpoints by required route values. /// - /// The used to generate the link. + /// The used to generate the link. /// The . public DispatcherUrlHelper( ActionContext actionContext, IEndpointFinder routeValuesBasedEndpointFinder, - ILinkGenerator linkGenerator, + LinkGenerator linkGenerator, ILogger logger) : base(actionContext) { @@ -94,6 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing }); var successfullyGeneratedLink = _linkGenerator.TryGetLink( + ActionContext.HttpContext, endpoints, valuesDictionary, AmbientValues, @@ -128,6 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing }); var successfullyGeneratedLink = _linkGenerator.TryGetLink( + ActionContext.HttpContext, endpoints, valuesDictionary, AmbientValues, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index d8c257ffdc..9d464aafa3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing if (endpointFeature?.Endpoint != null) { var services = httpContext.RequestServices; - var linkGenerator = services.GetRequiredService(); + var linkGenerator = services.GetRequiredService(); var routeValuesBasedEndpointFinder = services.GetRequiredService>(); var logger = services.GetRequiredService>(); From dd252c0ccc22c1ba2381d59ee4634f0ab1d570c9 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 17 Jul 2018 05:24:51 -0700 Subject: [PATCH 106/316] Updated dependencies.props to use feature branch versions --- build/dependencies.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3577022de9..f253ec51ed 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -39,7 +39,7 @@ 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-a-preview1-abstract-linkgenerator-16720 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 @@ -48,8 +48,8 @@ 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-a-preview1-abstract-linkgenerator-16742 + 2.2.0-a-preview1-abstract-linkgenerator-16742 2.2.0-preview1-34694 2.2.0-preview1-34694 2.2.0-preview1-34694 From c1fab727a062dd82d2ba686cbfa62adb94519b03 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 17 Jul 2018 10:09:36 -0700 Subject: [PATCH 107/316] Upgraded dependencies.props --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index f253ec51ed..542e600ae9 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 2.2.0-preview1-17099 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-a-preview1-abstract-linkgenerator-16720 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-a-preview1-abstract-linkgenerator-16742 - 2.2.0-a-preview1-abstract-linkgenerator-16742 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 1.7.0 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 2.1.0 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34694 - 2.2.0-preview1-34694 + 2.2.0-preview1-34709 + 2.2.0-preview1-34709 15.6.1 4.7.49 2.0.3 From badbcb94374abd5f30a4bc8b86c0af85e517b823 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 18 Jul 2018 15:48:18 +1200 Subject: [PATCH 108/316] Add MvcEndpointDataSource benchmarks (#8104) --- .../MvcEndpointDatasourceBenchmark.cs | 131 ++++++++++++++++++ .../Properties/AssemblyInfo.cs | 2 + 2 files changed, 133 insertions(+) create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs new file mode 100644 index 0000000000..982b8d4e22 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging.Abstractions; + +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public class MvcEndpointDataSourceBenchmark + { + private const string DefaultRoute = "{Controller=Home}/{Action=Index}/{id?}"; + + private MockActionDescriptorCollectionProvider _conventionalActionProvider; + private MockActionDescriptorCollectionProvider _attributeActionProvider; + private List _conventionalEndpointInfos; + + [Params(1, 100, 1000)] + public int ActionCount; + + [GlobalSetup] + public void Setup() + { + _conventionalActionProvider = new MockActionDescriptorCollectionProvider( + Enumerable.Range(0, ActionCount).Select(i => CreateActionDescriptor(i, false)).ToList() + ); + + _attributeActionProvider = new MockActionDescriptorCollectionProvider( + Enumerable.Range(0, ActionCount).Select(i => CreateActionDescriptor(i, true)).ToList() + ); + + _conventionalEndpointInfos = new List + { + new MvcEndpointInfo( + "Default", + DefaultRoute, + new RouteValueDictionary(), + new Dictionary(), + new RouteValueDictionary(), + new MockInlineConstraintResolver()) + }; + } + + [Benchmark] + public void AttributeRouteEndpoints() + { + var endpointDataSource = CreateMvcEndpointDataSource(_attributeActionProvider); + var endpoints = endpointDataSource.Endpoints; + } + + [Benchmark] + public void ConventionalEndpoints() + { + var endpointDataSource = CreateMvcEndpointDataSource(_conventionalActionProvider); + endpointDataSource.ConventionalEndpointInfos.AddRange(_conventionalEndpointInfos); + var endpoints = endpointDataSource.Endpoints; + } + + private ActionDescriptor CreateActionDescriptor(int id, bool attributeRoute) + { + var actionDescriptor = new ActionDescriptor + { + RouteValues = new Dictionary + { + ["Controller"] = "Controller" + id, + ["Action"] = "Index" + }, + DisplayName = "Action " + id + }; + + if (attributeRoute) + { + actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo + { + Template = DefaultRoute + }; + } + + return actionDescriptor; + } + + private MvcEndpointDataSource CreateMvcEndpointDataSource( + IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) + { + var dataSource = new MvcEndpointDataSource( + actionDescriptorCollectionProvider, + new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), + Array.Empty(), + new MockServiceProvider()); + + return dataSource; + } + + private class MockActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider + { + public MockActionDescriptorCollectionProvider(List actionDescriptors) + { + ActionDescriptors = new ActionDescriptorCollection(actionDescriptors, 0); + } + + public ActionDescriptorCollection ActionDescriptors { get; } + } + + private class MockServiceProvider : IServiceProvider + { + public object GetService(Type serviceType) + { + throw new NotImplementedException(); + } + } + + private class MockInlineConstraintResolver : IInlineConstraintResolver + { + public IRouteConstraint ResolveConstraint(string inlineConstraint) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs index 31781090a1..14bede0243 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs @@ -36,4 +36,6 @@ using Microsoft.AspNetCore.Mvc.Formatters; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Views.TestCommon, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2, PublicKey=0024000004800000940000000602000000240000525341310004000001000100c547cac37abd99c8db225ef2f6c8a3602f3b3606cc9891605d02baa56104f4cfc0734aa39b93bf7852f7d9266654753cc297e7d2edfe0bac1cdcf9f717241550e0a7b191195b7667bb4f64bcb8e2121380fd1d9d46ad2d92d2d15605093924cceaf74c4861eff62abf69b9291ed0a340e113be11e6a7d3113e92484cf7045cc7")] From ec8976ffaf9b91e2e80410414aefd213bf85f521 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 19 Jul 2018 16:43:24 +1200 Subject: [PATCH 109/316] Update MvcEndpointDataSource on raised change token (#8108) --- .../Internal/MvcEndpointDataSource.cs | 57 ++++----- .../Internal/MvcEndpointDataSourceTests.cs | 109 ++++++++++++++++-- 2 files changed, 129 insertions(+), 37 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 2d181e9754..a76dbe8c93 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -19,15 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal { internal class MvcEndpointDataSource : EndpointDataSource { + private readonly object _lock = new object(); private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; private readonly IServiceProvider _serviceProvider; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; - private readonly List _endpoints; - private readonly object _lock = new object(); - private IChangeToken _changeToken; - private bool _initialized; + private List _endpoints; public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, @@ -60,12 +58,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal _serviceProvider = serviceProvider; _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); - _endpoints = new List(); ConventionalEndpointInfos = new List(); + + Extensions.Primitives.ChangeToken.OnChange( + GetCompositeChangeToken, + UpdateEndpoints); } - private void InitializeEndpoints() + private List CreateEndpoints() { + List endpoints = new List(); + foreach (var action in _actions.ActionDescriptors.Items) { if (action.AttributeRouteInfo == null) @@ -117,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo); - _endpoints.Add(subEndpoint); + endpoints.Add(subEndpoint); } var segment = newEndpointTemplate.Segments[i]; @@ -142,7 +145,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo); - _endpoints.Add(endpoint); + endpoints.Add(endpoint); } } } @@ -155,9 +158,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo); - _endpoints.Add(endpoint); + endpoints.Add(endpoint); } } + + return endpoints; } private bool IsMvcParameter(string name) @@ -392,36 +397,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal return new CompositeChangeToken(changeTokens); } - public override IChangeToken ChangeToken - { - get - { - if (_changeToken == null) - { - _changeToken = GetCompositeChangeToken(); - } - - return _changeToken; - } - } + public override IChangeToken ChangeToken => GetCompositeChangeToken(); public override IReadOnlyList Endpoints { get { - if (!_initialized) + // Want to initialize endpoints once and then cache while ensuring a null collection is never returned + // Local copy for thread safety + double check locking + var localEndpoints = _endpoints; + if (localEndpoints == null) { lock (_lock) { - if (!_initialized) + localEndpoints = _endpoints; + if (localEndpoints == null) { - InitializeEndpoints(); - _initialized = true; + _endpoints = localEndpoints = CreateEndpoints(); } } } - return _endpoints; + return localEndpoints; + } + } + + private void UpdateEndpoints() + { + lock (_lock) + { + _endpoints = CreateEndpoints(); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 635169ee32..983af123c6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; @@ -183,7 +184,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })] //[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })] //[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })] - public void InitializeEndpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -210,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] - public void InitializeEndpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -231,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void InitializeEndpoints_SingleAction_WithActionDefault() + public void Endpoints_SingleAction_WithActionDefault() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -252,7 +253,93 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void InitializeEndpoints_MultipleActions_WithActionConstraint() + public void Endpoints_CalledMultipleTimes_ReturnsSameInstance() + { + // Arrange + var actionDescriptorCollectionProviderMock = new Mock(); + actionDescriptorCollectionProviderMock + .Setup(m => m.ActionDescriptors) + .Returns(new ActionDescriptorCollection(new[] + { + CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }) + }, version: 0)); + + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollectionProviderMock.Object); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}", + new RouteValueDictionary(new { action = "TestAction" }))); + + // Act + var endpoints1 = dataSource.Endpoints; + var endpoints2 = dataSource.Endpoints; + + // Assert + Assert.Collection(endpoints1, + (e) => Assert.Equal("TestController", Assert.IsType(e).Template), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + Assert.Same(endpoints1, endpoints2); + + actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once); + } + + [Fact] + public void Endpoints_ChangeTokenTriggered_EndpointsRecreated() + { + // Arrange + var actionDescriptorCollectionProviderMock = new Mock(); + actionDescriptorCollectionProviderMock + .Setup(m => m.ActionDescriptors) + .Returns(new ActionDescriptorCollection(new[] + { + CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }) + }, version: 0)); + + CancellationTokenSource cts = null; + + var changeProviderMock = new Mock(); + changeProviderMock.Setup(m => m.GetChangeToken()).Returns(() => + { + cts = new CancellationTokenSource(); + var changeToken = new CancellationChangeToken(cts.Token); + + return changeToken; + }); + + var dataSource = CreateMvcEndpointDataSource( + actionDescriptorCollectionProviderMock.Object, + actionDescriptorChangeProviders: new[] { changeProviderMock.Object }); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}", + new RouteValueDictionary(new { action = "TestAction" }))); + + // Act + var endpoints = dataSource.Endpoints; + + Assert.Collection(endpoints, + (e) => Assert.Equal("TestController", Assert.IsType(e).Template), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + + actionDescriptorCollectionProviderMock + .Setup(m => m.ActionDescriptors) + .Returns(new ActionDescriptorCollection(new[] + { + CreateActionDescriptor(new { controller = "NewTestController", action = "NewTestAction" }) + }, version: 1)); + + cts.Cancel(); + + // Assert + var newEndpoints = dataSource.Endpoints; + + Assert.NotSame(endpoints, newEndpoints); + Assert.Collection(newEndpoints, + (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).Template)); + } + + [Fact] + public void Endpoints_MultipleActions_WithActionConstraint() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -277,7 +364,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })] [InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })] - public void InitializeEndpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates) + public void Endpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -302,7 +389,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void ConventionalRoute_WithNoRouteName_DoesNotAddRouteNameMetadata() + public void Endpoints_ConventionalRoute_WithNoRouteName_DoesNotAddRouteNameMetadata() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -322,7 +409,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void CanCreateMultipleEndpoints_WithSameRouteName() + public void Endpoints_CanCreateMultipleEndpoints_WithSameRouteName() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -357,7 +444,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void InitializeEndpoints_ConventionalRoutes_StaticallyDefinedOrder_IsMaintained() + public void Endpoints_ConventionalRoutes_StaticallyDefinedOrder_IsMaintained() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -619,11 +706,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionDescriptors.Add(CreateActionDescriptor(requiredValue)); } - var actionDescriptorCollectionProvider = new Mock(); - actionDescriptorCollectionProvider + var actionDescriptorCollectionProviderMock = new Mock(); + actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) .Returns(new ActionDescriptorCollection(actionDescriptors, version: 0)); - return actionDescriptorCollectionProvider.Object; + return actionDescriptorCollectionProviderMock.Object; } private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) From 36d90c9bc2106847c3db3d4171d36570cf12e9a2 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 19 Jul 2018 16:43:51 +1200 Subject: [PATCH 110/316] Run request service constraint test with dispatching (#8112) --- .../RequestServicesDispatchingTest.cs | 13 +++ .../RequestServicesTest.cs | 84 +--------------- .../RequestServicesTestBase.cs | 99 +++++++++++++++++++ .../RequestScopedServiceController.cs | 4 +- .../RequestScopedActionConstraint.cs | 35 +++++-- .../BasicWebSite/StartupWithDispatching.cs | 6 ++ 6 files changed, 150 insertions(+), 91 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs new file mode 100644 index 0000000000..f725d9ed92 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.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 RequestServicesDispatchingTest : RequestServicesTestBase + { + public RequestServicesDispatchingTest(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs index 29fd69112c..f62521bd6c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs @@ -1,93 +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; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - // Each of these tests makes two requests, because we want each test to verify that the data is - // PER-REQUEST and does not linger around to impact the next request. - public class RequestServicesTest : IClassFixture> + public class RequestServicesTest : RequestServicesTestBase { public RequestServicesTest(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Theory] - [InlineData("http://localhost/RequestScopedService/FromFilter")] - [InlineData("http://localhost/RequestScopedService/FromView")] - [InlineData("http://localhost/RequestScopedService/FromViewComponent")] - [InlineData("http://localhost/RequestScopedService/FromActionArgument")] - public async Task RequestServices(string url) - { - for (var i = 0; i < 2; i++) - { - // Arrange - var requestId = Guid.NewGuid().ToString(); - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.TryAddWithoutValidation("RequestId", requestId); - - // Act - var response = await Client.SendAsync(request); - - // Assert - response.EnsureSuccessStatusCode(); - var body = (await response.Content.ReadAsStringAsync()).Trim(); - Assert.Equal(requestId, body); - } - } - - [Fact] - public async Task RequestServices_TagHelper() - { - // Arrange - var url = "http://localhost/RequestScopedService/FromTagHelper"; - - // Act & Assert - for (var i = 0; i < 2; i++) - { - var requestId = Guid.NewGuid().ToString(); - var request = new HttpRequestMessage(HttpMethod.Get, url); - request.Headers.TryAddWithoutValidation("RequestId", requestId); - - var response = await Client.SendAsync(request); - - var body = (await response.Content.ReadAsStringAsync()).Trim(); - - var expected = "" + requestId + ""; - Assert.Equal(expected, body); - } - } - - [Fact] - public async Task RequestServices_ActionConstraint() - { - // Arrange - var url = "http://localhost/RequestScopedService/FromActionConstraint"; - - // Act & Assert - var requestId1 = "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5"; - var request1 = new HttpRequestMessage(HttpMethod.Get, url); - request1.Headers.TryAddWithoutValidation("RequestId", requestId1); - - var response1 = await Client.SendAsync(request1); - - var body1 = (await response1.Content.ReadAsStringAsync()).Trim(); - Assert.Equal(requestId1, body1); - - var requestId2 = Guid.NewGuid().ToString(); - var request2 = new HttpRequestMessage(HttpMethod.Get, url); - request2.Headers.TryAddWithoutValidation("RequestId", requestId2); - - var response2 = await Client.SendAsync(request2); - Assert.Equal(HttpStatusCode.NotFound, response2.StatusCode); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs new file mode 100644 index 0000000000..425bda5ea5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs @@ -0,0 +1,99 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + // Each of these tests makes two requests, because we want each test to verify that the data is + // PER-REQUEST and does not linger around to impact the next request. + public abstract class RequestServicesTestBase : IClassFixture> where TStartup : class + { + protected RequestServicesTestBase(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("http://localhost/RequestScopedService/FromFilter")] + [InlineData("http://localhost/RequestScopedService/FromView")] + [InlineData("http://localhost/RequestScopedService/FromViewComponent")] + [InlineData("http://localhost/RequestScopedService/FromActionArgument")] + public async Task RequestServices(string url) + { + for (var i = 0; i < 2; i++) + { + // Arrange + var requestId = Guid.NewGuid().ToString(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.TryAddWithoutValidation("RequestId", requestId); + + // Act + var response = await Client.SendAsync(request); + + // Assert + response.EnsureSuccessStatusCode(); + var body = (await response.Content.ReadAsStringAsync()).Trim(); + Assert.Equal(requestId, body); + } + } + + [Fact] + public async Task RequestServices_TagHelper() + { + // Arrange + var url = "http://localhost/RequestScopedService/FromTagHelper"; + + // Act & Assert + for (var i = 0; i < 2; i++) + { + var requestId = Guid.NewGuid().ToString(); + var request = new HttpRequestMessage(HttpMethod.Get, url); + request.Headers.TryAddWithoutValidation("RequestId", requestId); + + var response = await Client.SendAsync(request); + + var body = (await response.Content.ReadAsStringAsync()).Trim(); + + var expected = "" + requestId + ""; + Assert.Equal(expected, body); + } + } + + [Fact] + public async Task RequestServices_Constraint() + { + // Arrange + var url = "http://localhost/RequestScopedService/FromConstraint"; + + // Act & Assert + var requestId1 = "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5"; + var request1 = new HttpRequestMessage(HttpMethod.Get, url); + request1.Headers.TryAddWithoutValidation("RequestId", requestId1); + + var response1 = await Client.SendAsync(request1); + + var body1 = (await response1.Content.ReadAsStringAsync()).Trim(); + Assert.Equal(requestId1, body1); + + var requestId2 = Guid.NewGuid().ToString(); + var request2 = new HttpRequestMessage(HttpMethod.Get, url); + request2.Headers.TryAddWithoutValidation("RequestId", requestId2); + + var response2 = await Client.SendAsync(request2); + Assert.Equal(HttpStatusCode.NotFound, response2.StatusCode); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs b/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs index e7a89377b3..a7bd552217 100644 --- a/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs +++ b/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs @@ -10,8 +10,8 @@ namespace BasicWebSite { // This only matches a specific requestId value [HttpGet] - [RequestScopedActionConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")] - public string FromActionConstraint() + [RequestScopedConstraint("b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5")] + public string FromConstraint() { return "b40f6ec1-8a6b-41c1-b3fe-928f581ebaf5"; } diff --git a/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs b/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs index 3a5be47cf4..0b351b389a 100644 --- a/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs +++ b/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs @@ -4,12 +4,13 @@ using System; using System.Collections.Concurrent; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite { // Only matches when the requestId is the same as the one passed in the constructor. - public class RequestScopedActionConstraintAttribute : Attribute, IActionConstraintFactory + public class RequestScopedConstraintAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory { private readonly string _requestId; private readonly Func CreateFactory = @@ -19,18 +20,28 @@ namespace BasicWebSite public bool IsReusable => false; - public RequestScopedActionConstraintAttribute(string requestId) + public RequestScopedConstraintAttribute(string requestId) { _requestId = requestId; } - public IActionConstraint CreateInstance(IServiceProvider services) + IActionConstraint IActionConstraintFactory.CreateInstance(IServiceProvider services) { - var constraintType = typeof(Constraint); - return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint),new[] { _requestId }); + return CreateInstanceCore(services); } - private class Constraint : IActionConstraint + IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) + { + return CreateInstanceCore(services); + } + + private Constraint CreateInstanceCore(IServiceProvider services) + { + var constraintType = typeof(Constraint); + return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint), new[] { _requestId }); + } + + private class Constraint : IActionConstraint, IEndpointConstraint { private readonly RequestIdService _requestIdService; private readonly string _requestId; @@ -43,7 +54,17 @@ namespace BasicWebSite public int Order { get; private set; } - public bool Accept(ActionConstraintContext context) + bool IActionConstraint.Accept(ActionConstraintContext context) + { + return AcceptCore(); + } + + bool IEndpointConstraint.Accept(EndpointConstraintContext context) + { + return AcceptCore(); + } + + private bool AcceptCore() { return _requestId == _requestIdService.RequestId; } diff --git a/test/WebSites/BasicWebSite/StartupWithDispatching.cs b/test/WebSites/BasicWebSite/StartupWithDispatching.cs index 4c5ed0dc1e..eb122b7290 100644 --- a/test/WebSites/BasicWebSite/StartupWithDispatching.cs +++ b/test/WebSites/BasicWebSite/StartupWithDispatching.cs @@ -19,10 +19,16 @@ namespace BasicWebSite .AddXmlDataContractSerializerFormatters(); services.ConfigureBaseWebSiteAuthPolicies(); + + services.AddHttpContextAccessor(); + services.AddScoped(); } public void Configure(IApplicationBuilder app) { + // Initializes the RequestId service for each request + app.UseMiddleware(); + app.UseDispatcher(); app.UseMvcWithEndpoint(routes => From 53930af0e3205c1c5df4963467c8163132925ab0 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Thu, 19 Jul 2018 23:33:12 +0200 Subject: [PATCH 111/316] Set ProblemDetails status field during ObjectResult formatting --- .../ObjectResult.cs | 5 +++ .../ObjectResultTests.cs | 33 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs index ded227d22a..8e850a363c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs @@ -50,6 +50,11 @@ namespace Microsoft.AspNetCore.Mvc if (StatusCode.HasValue) { context.HttpContext.Response.StatusCode = StatusCode.Value; + + if (Value is ProblemDetails details && !details.Status.HasValue) + { + details.Status = StatusCode.Value; + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs index 2c0e9ab003..7b818ad97b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -61,6 +62,38 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(404, actionContext.HttpContext.Response.StatusCode); } + [Fact] + public async Task ObjectResult_ExecuteResultAsync_SetsProblemDetailsStatus() + { + // Arrange + var modelState = new ModelStateDictionary(); + + var details = new ValidationProblemDetails(modelState); + + var result = new ObjectResult(details) + { + StatusCode = StatusCodes.Status422UnprocessableEntity, + Formatters = new FormatterCollection() + { + new NoOpOutputFormatter(), + }, + }; + + var actionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext() + { + RequestServices = CreateServices(), + } + }; + + // Act + await result.ExecuteResultAsync(actionContext); + + // Assert + Assert.Equal(StatusCodes.Status422UnprocessableEntity, details.Status.Value); + } + private static IServiceProvider CreateServices() { var services = new ServiceCollection(); From 3ba6f3549576fc411df28d1547feff46bbec4533 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 19 Jul 2018 22:03:39 -0700 Subject: [PATCH 112/316] React to RoutePattern changes in Routing --- .../Internal/MvcEndpointDataSource.cs | 13 ++--- .../ConsumesAttributeTests.cs | 6 +- .../Internal/MvcEndpointDataSourceTests.cs | 58 ++++++++++--------- .../Routing/DispatcherUrlHelperTest.cs | 29 +++++----- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index a76dbe8c93..c8738bb20e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; @@ -298,20 +299,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal return invoker.InvokeAsync(); }; + var defaults = new RouteValueDictionary(nonInlineDefaults); + EnsureRequiredValuesInDefaults(action.RouteValues, defaults); + var metadataCollection = BuildEndpointMetadata(action, routeName, source); var endpoint = new MatcherEndpoint( next => invokerDelegate, - template, - new RouteValueDictionary(nonInlineDefaults), + RoutePatternFactory.Parse(template, defaults, constraints: null), 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; } @@ -373,7 +372,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Required values: controller=foo, action=bar // Final constructed template: foo/bar/{category}/{id?} // Final defaults: controller=foo, action=bar, category=products - private void EnsureRequiredValuesInDefaults(RouteValueDictionary requiredValues, RouteValueDictionary defaults) + private void EnsureRequiredValuesInDefaults(IDictionary requiredValues, RouteValueDictionary defaults) { foreach (var kvp in requiredValues) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs index 83bae7b4b0..30514d5ac8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -297,12 +298,11 @@ namespace Microsoft.AspNetCore.Mvc private MatcherEndpoint CreateEndpoint(params IEndpointConstraint[] constraints) { - EndpointMetadataCollection endpointMetadata = new EndpointMetadataCollection(constraints); + var endpointMetadata = new EndpointMetadataCollection(constraints); return new MatcherEndpoint( (r) => null, - "", - new RouteValueDictionary(), + RoutePatternFactory.Parse("/"), new RouteValueDictionary(), 0, endpointMetadata, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 983af123c6..fb0df12fd3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(displayName, matcherEndpoint.DisplayName); Assert.Equal(order, matcherEndpoint.Order); - Assert.Equal(template, matcherEndpoint.Template); + Assert.Equal(template, matcherEndpoint.RoutePattern.RawText); } [Fact] @@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -224,7 +224,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -248,8 +248,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints, - (e) => Assert.Equal("TestController", Assert.IsType(e).Template), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); } [Fact] @@ -276,8 +276,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints1, - (e) => Assert.Equal("TestController", Assert.IsType(e).Template), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); Assert.Same(endpoints1, endpoints2); actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once); @@ -318,8 +318,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; Assert.Collection(endpoints, - (e) => Assert.Equal("TestController", Assert.IsType(e).Template), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).Template)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) @@ -335,7 +335,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotSame(endpoints, newEndpoints); Assert.Collection(newEndpoints, - (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).Template)); + (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).RoutePattern.RawText)); } [Fact] @@ -357,8 +357,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints, - (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).Template), - (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).Template)); + (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).RoutePattern.RawText)); } [Theory] @@ -381,7 +381,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).Template))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -431,7 +431,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeNameMetadata); Assert.Equal("namedRoute", routeNameMetadata.Name); - Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template); + Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); }, (ep) => { @@ -439,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeNameMetadata); Assert.Equal("namedRoute", routeNameMetadata.Name); - Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template); + Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); }); } @@ -467,25 +467,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal (ep) => { var matcherEndpoint = Assert.IsType(ep); - Assert.Equal("Home/Index/{id?}", matcherEndpoint.Template); + Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(1, matcherEndpoint.Order); }, (ep) => { var matcherEndpoint = Assert.IsType(ep); - Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.Template); + Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(2, matcherEndpoint.Order); }, (ep) => { var matcherEndpoint = Assert.IsType(ep); - Assert.Equal("Products/Details/{id?}", matcherEndpoint.Template); + Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(1, matcherEndpoint.Order); }, (ep) => { var matcherEndpoint = Assert.IsType(ep); - Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.Template); + Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(2, matcherEndpoint.Order); }); } @@ -587,8 +587,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar", matcherEndpoint.Template); - AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } [Fact] @@ -609,8 +609,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar", matcherEndpoint.Template); - AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } [Fact] @@ -632,8 +632,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.Template); - AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } [Fact] @@ -654,8 +654,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar", matcherEndpoint.Template); - AssertIsSubset(expectedDefaults, matcherEndpoint.Defaults); + Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } private MvcEndpointDataSource CreateMvcEndpointDataSource( @@ -729,7 +729,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal return actionDescriptor; } - private void AssertIsSubset(RouteValueDictionary subset, RouteValueDictionary fullSet) + private void AssertIsSubset( + IReadOnlyDictionary subset, + IReadOnlyDictionary fullSet) { foreach (var subsetPair in subset) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs index 5d52f760a7..b069feabc6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; using Xunit; @@ -35,8 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var endpoints = GetDefaultEndpoints(); endpoints.Add(new MatcherEndpoint( next => httpContext => Task.CompletedTask, - template, - new RouteValueDictionary(), + RoutePatternFactory.Parse(template), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, @@ -51,8 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { Endpoint = new MatcherEndpoint( next => cntxt => Task.CompletedTask, - "/", - new RouteValueDictionary(), + RoutePatternFactory.Parse("/"), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, @@ -100,14 +99,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing private List GetDefaultEndpoints() { var endpoints = new List(); - endpoints.Add(CreateEndpoint(null, "home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 1)); - endpoints.Add(CreateEndpoint(null, "home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 2)); - endpoints.Add(CreateEndpoint(null, "home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3)); - endpoints.Add(CreateEndpoint(null, "home2/contact/{id?}", new { id = "defaultid", controller = "home2", action = "contact" }, 4)); - endpoints.Add(CreateEndpoint(null, "home3/contact/{id?}", new { id = "defaultid", controller = "home3", action = "contact" }, 5)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id?}", new { id = "defaultid", controller = "home", action = "newaction" }, 6)); - endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id?}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id?}", new { id = "defaultid", controller = "home", action = "contact" }, 8)); + endpoints.Add(CreateEndpoint(null, "home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 1)); + endpoints.Add(CreateEndpoint(null, "home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 2)); + endpoints.Add(CreateEndpoint(null, "home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3)); + endpoints.Add(CreateEndpoint(null, "home2/contact/{id}", new { id = "defaultid", controller = "home2", action = "contact" }, 4)); + endpoints.Add(CreateEndpoint(null, "home3/contact/{id}", new { id = "defaultid", controller = "home3", action = "contact" }, 5)); + endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 6)); + endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7)); + endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 8)); endpoints.Add(CreateEndpoint("MyRouteName", "any/url", new { }, 9)); return endpoints; } @@ -122,8 +121,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new MatcherEndpoint( next => (httpContext) => Task.CompletedTask, - template, - new RouteValueDictionary(defaults), + RoutePatternFactory.Parse(template, defaults, constraints: null), new RouteValueDictionary(), order, metadata, @@ -149,8 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { return new MatcherEndpoint( next => c => Task.CompletedTask, - template, - defaults, + RoutePatternFactory.Parse(template, defaults, constraints: null), new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, From 8f8d3afd3605dfed15127b9af0b8adcecc674890 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 20 Jul 2018 10:59:43 -0700 Subject: [PATCH 113/316] Fix bug in benchmark --- .../MvcEndpointDatasourceBenchmark.cs | 52 ++++++++++++------- 1 file changed, 33 insertions(+), 19 deletions(-) diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs index 982b8d4e22..bb66f15d28 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -4,17 +4,13 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; using BenchmarkDotNet.Attributes; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Logging.Abstractions; namespace Microsoft.AspNetCore.Mvc.Performance { @@ -22,6 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.Performance { private const string DefaultRoute = "{Controller=Home}/{Action=Index}/{id?}"; + // Attribute routes can't have controller and action as parameters, so we edit the + // route template in the test to make it more realistic. + private const string ControllerReplacementToken = "{Controller=Home}"; + private const string ActionReplacementToken = "{Action=Index}"; + private MockActionDescriptorCollectionProvider _conventionalActionProvider; private MockActionDescriptorCollectionProvider _attributeActionProvider; private List _conventionalEndpointInfos; @@ -33,11 +34,11 @@ namespace Microsoft.AspNetCore.Mvc.Performance public void Setup() { _conventionalActionProvider = new MockActionDescriptorCollectionProvider( - Enumerable.Range(0, ActionCount).Select(i => CreateActionDescriptor(i, false)).ToList() + Enumerable.Range(0, ActionCount).Select(i => CreateConventionalRoutedAction(i)).ToList() ); _attributeActionProvider = new MockActionDescriptorCollectionProvider( - Enumerable.Range(0, ActionCount).Select(i => CreateActionDescriptor(i, true)).ToList() + Enumerable.Range(0, ActionCount).Select(i => CreateAttributeRoutedAction(i)).ToList() ); _conventionalEndpointInfos = new List @@ -67,27 +68,40 @@ namespace Microsoft.AspNetCore.Mvc.Performance var endpoints = endpointDataSource.Endpoints; } - private ActionDescriptor CreateActionDescriptor(int id, bool attributeRoute) + private ActionDescriptor CreateAttributeRoutedAction(int id) { - var actionDescriptor = new ActionDescriptor + var routeValues = new Dictionary(StringComparer.OrdinalIgnoreCase) { - RouteValues = new Dictionary + ["Controller"] = "Controller" + id, + ["Action"] = "Index" + }; + + var template = DefaultRoute + .Replace(ControllerReplacementToken, routeValues["Controller"]) + .Replace(ActionReplacementToken, routeValues["Action"]); + + return new ActionDescriptor + { + RouteValues = routeValues, + DisplayName = "Action " + id, + AttributeRouteInfo = new AttributeRouteInfo() + { + Template = template, + } + }; + } + + private ActionDescriptor CreateConventionalRoutedAction(int id) + { + return new ActionDescriptor + { + RouteValues = new Dictionary(StringComparer.OrdinalIgnoreCase) { ["Controller"] = "Controller" + id, ["Action"] = "Index" }, DisplayName = "Action " + id }; - - if (attributeRoute) - { - actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo - { - Template = DefaultRoute - }; - } - - return actionDescriptor; } private MvcEndpointDataSource CreateMvcEndpointDataSource( From 5c488bf09ca5341f873331ef22b051da66edc325 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 20 Jul 2018 11:08:24 -0700 Subject: [PATCH 114/316] make feature branch build --- Directory.Build.props | 1 + build/dependencies.props | 4 ++-- .../Microsoft.AspNetCore.Mvc.Core.csproj | 1 + .../Microsoft.AspNetCore.Mvc.FunctionalTests.csproj | 8 ++++++++ test/WebSites/BasicWebSite/BasicWebSite.csproj | 8 ++++++++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index d37be59e20..065616f3cd 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,6 +18,7 @@ MicrosoftNuGet true true + true diff --git a/build/dependencies.props b/build/dependencies.props index 542e600ae9..33521fbe2c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34709 2.2.0-preview1-34709 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 + 2.2.0-a-preview1-route-pattern-16756 + 2.2.0-a-preview1-route-pattern-16756 2.2.0-preview1-34709 2.2.0-preview1-34709 2.2.0-preview1-34709 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 7fc4891804..0698b3fd85 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj @@ -25,6 +25,7 @@ Microsoft.AspNetCore.Mvc.RouteAttribute + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index ac7be75576..1e7cb2f215 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -50,6 +50,14 @@ + + + + + diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/test/WebSites/BasicWebSite/BasicWebSite.csproj index 55879651a0..b71fbe1efc 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.csproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.csproj @@ -10,6 +10,14 @@ + + + + + From d4beab5d09bb28e970063f7da9fe916b99514c5c Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Mon, 16 Jul 2018 12:42:17 +0200 Subject: [PATCH 115/316] CompositeValidationAttribute - Add abstract CompositeValidationAttribute. - Change DataAnnotationsMetadataProvider.CreateValidationMetadata to populate ValidatorMetadata with validation attributes from CompositeValidationAttribute. --- .../DataAnnotationsMetadataProvider.cs | 19 +++++- .../ValidationProviderAttribute.cs | 22 +++++++ .../DataAnnotationsMetadataProviderTest.cs | 47 ++++++++++++++ .../HtmlGenerationTest.cs | 46 ++++++++++++++ .../Infrastructure/HttpClientExtensions.cs | 5 ++ .../InputObjectValidationTests.cs | 63 ++++++++++++++++++- .../Controllers/ValidationController.cs | 11 ++++ .../ValidationProviderAttributeModel.cs | 43 +++++++++++++ .../HtmlGeneration_HomeController.cs | 5 ++ .../ValidationProviderAttributeModel.cs | 43 +++++++++++++ .../ValidationProviderAttribute.cshtml | 24 +++++++ 11 files changed, 325 insertions(+), 3 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs create mode 100644 test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs create mode 100644 test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs create mode 100644 test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs index b91e4b9501..d0a2875c5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs @@ -317,15 +317,30 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal throw new ArgumentNullException(nameof(context)); } + var attributes = new List(context.Attributes.Count); + + for (var i = 0; i < context.Attributes.Count; i++) + { + var attribute = context.Attributes[i]; + if (attribute is ValidationProviderAttribute validationProviderAttribute) + { + attributes.AddRange(validationProviderAttribute.GetValidationAttributes()); + } + else + { + attributes.Add(attribute); + } + } + // RequiredAttribute marks a property as required by validation - this means that it // must have a non-null value on the model during validation. - var requiredAttribute = context.Attributes.OfType().FirstOrDefault(); + var requiredAttribute = attributes.OfType().FirstOrDefault(); if (requiredAttribute != null) { context.ValidationMetadata.IsRequired = true; } - foreach (var attribute in context.Attributes.OfType()) + foreach (var attribute in attributes.OfType()) { // If another provider has already added this attribute, do not repeat it. // This will prevent attributes like RemoteAttribute (which implement ValidationAttribute and diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs new file mode 100644 index 0000000000..c745850b64 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; + +namespace Microsoft.AspNetCore.Mvc.DataAnnotations +{ + /// + /// Abstract class for grouping attributes of type into + /// one + /// + public abstract class ValidationProviderAttribute : Attribute + { + /// + /// Gets instances associated with this attribute. + /// + /// Sequence of associated with this attribute. + public abstract IEnumerable GetValidationAttributes(); + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs index 10dfe11b01..0f18010965 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs @@ -1241,6 +1241,38 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal Assert.Equal(initialValue, context.ValidationMetadata.IsRequired); } + [Fact] + public void CreateValidationMetadata_WillAddValidationAttributes_From_ValidationProviderAttribute() + { + // Arrange + var provider = new DataAnnotationsMetadataProvider( + Options.Create(new MvcDataAnnotationsLocalizationOptions()), + stringLocalizerFactory: null); + var validationProviderAttribute = new FooCompositeValidationAttribute( + attributes: new List + { + new RequiredAttribute(), + new StringLengthAttribute(5) + }); + + var attributes = new Attribute[] { new EmailAddressAttribute(), validationProviderAttribute }; + var key = ModelMetadataIdentity.ForProperty(typeof(string), "Length", typeof(string)); + var context = new ValidationMetadataProviderContext(key, GetModelAttributes(new object[0], attributes)); + + // Act + provider.CreateValidationMetadata(context); + + // Assert + var expected = new List + { + new EmailAddressAttribute(), + new RequiredAttribute(), + new StringLengthAttribute(5) + }; + + Assert.Equal(expected, actual: context.ValidationMetadata.ValidatorMetadata); + } + // [Required] has no effect on IsBindingRequired [Theory] [InlineData(true)] @@ -1545,5 +1577,20 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal public string Name { get; private set; } } + + private class FooCompositeValidationAttribute : ValidationProviderAttribute + { + private IEnumerable _attributes; + + public FooCompositeValidationAttribute(IEnumerable attributes) + { + _attributes = attributes; + } + + public override IEnumerable GetValidationAttributes() + { + return _attributes; + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index 32cb029485..8c7984814e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -613,6 +614,51 @@ Products: Music Systems, Televisions (3)"; Assert.Empty(content.TextContent); } + [Fact] + public async Task ValidationProviderAttribute_ValidationTagHelpers_GeneratesExpectedDataAttributes() + { + // Act + var document = await Client.GetHtmlDocumentAsync("HtmlGeneration_Home/ValidationProviderAttribute"); + + // Assert + var firstName = document.RequiredQuerySelector("#FirstName"); + Assert.Equal("true", firstName.GetAttribute("data-val")); + Assert.Equal("The FirstName field is required.", firstName.GetAttribute("data-val-required")); + Assert.Equal("The field FirstName must be a string with a maximum length of 5.", firstName.GetAttribute("data-val-length")); + Assert.Equal("5", firstName.GetAttribute("data-val-length-max")); + Assert.Equal("The field FirstName must match the regular expression '[A-Za-z]*'.", firstName.GetAttribute("data-val-regex")); + Assert.Equal("[A-Za-z]*", firstName.GetAttribute("data-val-regex-pattern")); + + var lastName = document.RequiredQuerySelector("#LastName"); + Assert.Equal("true", lastName.GetAttribute("data-val")); + Assert.Equal("The LastName field is required.", lastName.GetAttribute("data-val-required")); + Assert.Equal("The field LastName must be a string with a maximum length of 6.", lastName.GetAttribute("data-val-length")); + Assert.Equal("6", lastName.GetAttribute("data-val-length-max")); + Assert.False(lastName.HasAttribute("data-val-regex")); + } + + [Fact] + public async Task ValidationProviderAttribute_ValidationTagHelpers_GeneratesExpectedSpansAndDivsOnValidationError() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "HtmlGeneration_Home/ValidationProviderAttribute"); + request.Content = new FormUrlEncodedContent(new Dictionary + { + { "FirstName", "TestFirstName" }, + }); + + // Act + var response = await Client.SendAsync(request); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var document = await response.GetHtmlDocumentAsync(); + Assert.Collection( + document.QuerySelectorAll("div.validation-summary-errors ul li"), + item => Assert.Equal("The field FirstName must be a string with a maximum length of 5.", item.TextContent), + item => Assert.Equal("The LastName field is required.", item.TextContent)); + } + private static HttpRequestMessage RequestWithLocale(string url, string locale) { var request = new HttpRequestMessage(HttpMethod.Get, url); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs index fe8d4f300b..94e52c7e14 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs @@ -18,6 +18,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var response = await client.GetAsync(requestUri); await AssertStatusCodeAsync(response, HttpStatusCode.OK); + return await GetHtmlDocumentAsync(response); + } + + public static async Task GetHtmlDocumentAsync(this HttpResponseMessage response) + { var content = await response.Content.ReadAsStringAsync(); var parser = new HtmlParser(); var document = parser.Parse(content); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs index 6a038fe5a1..05e12e9f64 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -7,7 +7,6 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing.xunit; using Newtonsoft.Json; using Xunit; @@ -182,5 +181,67 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal("xyz", await response.Content.ReadAsStringAsync()); } + + [Fact] + public async Task ValidationProviderAttribute_WillValidateObject() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"TestName123\", \"LastName\": \"Test\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedErrorMessage = + "{\"FirstName\":[\"The field FirstName must match the regular expression '[A-Za-z]*'.\"," + + "\"The field FirstName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedErrorMessage, actual: responseContent); + } + + [Fact] + public async Task ValidationProviderAttribute_DoesNotInterfere_WithOtherValidationAttributes() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"Test\", \"LastName\": \"Testsson\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedErrorMessage = + "{\"LastName\":[\"The field LastName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedErrorMessage, actual: responseContent); + } + + [Fact] + public async Task ValidationProviderAttribute_RequiredAttributeErrorMessage_WillComeFirst() + { + // Arrange + var invalidRequestData = "{\"FirstName\":\"Testname\", \"LastName\": \"\"}"; + var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); + var expectedError = + "{\"LastName\":[\"The LastName field is required.\"]," + + "\"FirstName\":[\"The field FirstName must be a string with a maximum length of 5.\"]}"; + + // Act + var response = await Client.PostAsync( + "http://localhost/Validation/ValidationProviderAttribute", content); + + // Assert + Assert.Equal(expected: StatusCodes.Status400BadRequest, actual: (int)response.StatusCode); + + var responseContent = await response.Content.ReadAsStringAsync(); + Assert.Equal(expectedError, actual: responseContent); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs index efc2441c22..9f2bd1b536 100644 --- a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs @@ -67,5 +67,16 @@ namespace FormatterWebSite { return Json(simpleTypePropertiesModel); } + + [HttpPost] + public IActionResult ValidationProviderAttribute([FromBody] ValidationProviderAttributeModel validationProviderAttributeModel) + { + if (!ModelState.IsValid) + { + return BadRequest(ModelState); + } + + return Ok(); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs b/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs new file mode 100644 index 0000000000..05a3a0da75 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs @@ -0,0 +1,43 @@ +// 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; + +namespace FormatterWebSite +{ + public class ValidationProviderAttributeModel + { + [FirstName] + public string FirstName { get; set; } + + [StringLength(maximumLength: 5)] + [LastName] + public string LastName { get; set; } + } + + public class FirstNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute(), + new RegularExpressionAttribute(pattern: "[A-Za-z]*"), + new StringLengthAttribute(maximumLength: 5) + }; + } + } + + public class LastNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute() + }; + } + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs b/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs index 27dd20482f..184055fb75 100644 --- a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs +++ b/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs @@ -238,6 +238,11 @@ namespace HtmlGenerationWebSite.Controllers return View(); } + public IActionResult ValidationProviderAttribute() => View(); + + [HttpPost] + public IActionResult ValidationProviderAttribute(ValidationProviderAttributeModel model) => View(model); + public IActionResult PartialTagHelperWithoutModel() => View(); public IActionResult StatusMessage() => View(new StatusMessageModel { Message = "Some status message"}); diff --git a/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs b/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs new file mode 100644 index 0000000000..4c6669f060 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs @@ -0,0 +1,43 @@ +// 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.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations; + +namespace HtmlGenerationWebSite.Models +{ + public class ValidationProviderAttributeModel + { + [FirstName] + public string FirstName { get; set; } + + [StringLength(maximumLength: 6)] + [LastName] + public string LastName { get; set; } + } + + public class FirstNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute(), + new RegularExpressionAttribute(pattern: "[A-Za-z]*"), + new StringLengthAttribute(maximumLength: 5) + }; + } + } + + public class LastNameAttribute : ValidationProviderAttribute + { + public override IEnumerable GetValidationAttributes() + { + return new List + { + new RequiredAttribute() + }; + } + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml new file mode 100644 index 0000000000..d9b9a9e1a9 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml @@ -0,0 +1,24 @@ +@model HtmlGenerationWebSite.Models.ValidationProviderAttributeModel +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers + + + +
+
+ + + +
+ +
+ + + +
+ +
+
+ +
+ + \ No newline at end of file From 28c0c4d128fae199d73f2f62ce5736482e273891 Mon Sep 17 00:00:00 2001 From: Remco Date: Wed, 11 Jul 2018 18:57:03 -0700 Subject: [PATCH 116/316] Add ability to override the testing web content root using environment variables --- .../WebApplicationFactory.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index 3e31e9b5aa..c04fe25f45 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -128,6 +128,11 @@ namespace Microsoft.AspNetCore.Mvc.Testing private void SetContentRoot(IWebHostBuilder builder) { + if (SetContentRootFromSetting(builder)) + { + return; + } + var metadataAttributes = GetContentRootMetadataAttributes( typeof(TEntryPoint).Assembly.FullName, typeof(TEntryPoint).Assembly.GetName().Name); @@ -161,6 +166,24 @@ namespace Microsoft.AspNetCore.Mvc.Testing } } + private static bool SetContentRootFromSetting(IWebHostBuilder builder) + { + // Attempt to look for TEST_CONTENTROOT_APPNAME in settings. This should result in looking for + // ASPNETCORE_TEST_CONTENTROOT_APPNAME environment variable. + var assemblyName = typeof(TEntryPoint).Assembly.GetName().Name; + var settingSuffix = assemblyName.ToUpperInvariant().Replace(".", "_"); + var settingName = $"TEST_CONTENTROOT_{settingSuffix}"; + + var settingValue = builder.GetSetting(settingName); + if (settingValue == null) + { + return false; + } + + builder.UseContentRoot(settingValue); + return true; + } + private WebApplicationFactoryContentRootAttribute[] GetContentRootMetadataAttributes( string tEntryPointAssemblyFullName, string tEntryPointAssemblyName) From e903bda94a149db63bd3784fa4af0b04ab01454b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 20 Jul 2018 17:09:20 -0700 Subject: [PATCH 117/316] Add test project to Mvc.NoFun.sln --- Mvc.NoFun.sln | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 2da90ff325..445f77a303 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 15.0.26730.03 @@ -114,6 +114,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{92D959F2-66B8-490A-BA33-DA4421EBC948}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -560,6 +562,18 @@ Global {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|Mixed Platforms.Build.0 = Release|Any CPU {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.ActiveCfg = Release|Any CPU {0772E545-A674-4165-9469-E3D79D88A4A8}.Release|x86.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|x86.ActiveCfg = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Debug|x86.Build.0 = Debug|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Any CPU.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.ActiveCfg = Release|Any CPU + {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -603,6 +617,7 @@ Global {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A} From e1e7ec0f289e0a468c2cef9b61795755373ecad6 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Sat, 21 Jul 2018 04:44:13 -0700 Subject: [PATCH 118/316] Enable couple of skipped Dispatching functional tests --- .../DispatchingTests.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs index 05525fc755..0cee884762 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs @@ -18,24 +18,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] - public override Task AttributeRoutedAction_InArea_ExplicitLeaveArea() - { - return Task.CompletedTask; - } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { return Task.CompletedTask; } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] - public override Task ConventionalRoutedAction_InArea_ExplicitLeaveArea() - { - return Task.CompletedTask; - } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task ConventionalRoutedAction_InArea_StaysInArea() { From c0ba374549d20825a2a51cc57efd51f7b7f61357 Mon Sep 17 00:00:00 2001 From: "kishan.anem" Date: Sun, 22 Jul 2018 17:55:06 +0530 Subject: [PATCH 119/316] Custom error messages with validation message tag helper #8035 #8035 PR #8087 https://github.com/aspnet/Razor/issues/2497 --- .../ValidationMessageTagHelper.cs | 36 +++++++++---------- .../ValidationMessageTagHelperTest.cs | 7 ++-- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs index 8858623013..85d8d07837 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs @@ -71,11 +71,24 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers }; } + string message = null; + if (!output.IsContentModified) + { + var tagHelperContent = await output.GetChildContentAsync(); + + // We check for whitespace to detect scenarios such as: + // + // + if (!tagHelperContent.IsEmptyOrWhiteSpace) + { + message = tagHelperContent.GetContent(); + } + } var tagBuilder = Generator.GenerateValidationMessage( ViewContext, For.ModelExplorer, For.Name, - message: null, + message: message, tag: null, htmlAttributes: htmlAttributes); @@ -84,27 +97,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers output.MergeAttributes(tagBuilder); // Do not update the content if another tag helper targeting this element has already done so. - if (!output.IsContentModified) + if (!output.IsContentModified && tagBuilder.HasInnerHtml) { - // We check for whitespace to detect scenarios such as: - // - // - var childContent = await output.GetChildContentAsync(); - if (childContent.IsEmptyOrWhiteSpace) - { - // Provide default message text (if any) since there was nothing useful in the Razor source. - if (tagBuilder.HasInnerHtml) - { - output.Content.SetHtmlContent(tagBuilder.InnerHtml); - } - } - else - { - output.Content.SetHtmlContent(childContent); - } + output.Content.SetHtmlContent(tagBuilder.InnerHtml); } } } } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs index 69522655cb..1c919635be 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs @@ -357,10 +357,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Theory] - [InlineData("Content of validation message", "Content of validation message")] - [InlineData("\r\n \r\n", "New HTML")] + [InlineData("Content of validation message", "Content of validation message", "New HTML")] + [InlineData("\r\n \r\n", null, "New HTML")] public async Task ProcessAsync_MergesTagBuilderFromGenerateValidationMessage( string childContent, + string expectedMessage, string expectedOutputContent) { // Arrange @@ -375,7 +376,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers It.IsAny(), It.IsAny(), It.IsAny(), - It.IsAny(), + expectedMessage, It.IsAny(), It.IsAny())) .Returns(tagBuilder); From 3c73a98357b12a337c6bea6c26f7d3220ac0eac9 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 22 Jul 2018 12:21:04 -0700 Subject: [PATCH 120/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 33521fbe2c..12a0121bad 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 2.2.0-preview1-17099 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-a-preview1-route-pattern-16756 - 2.2.0-a-preview1-route-pattern-16756 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 1.7.0 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 2.1.0 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34709 - 2.2.0-preview1-34709 + 2.2.0-preview1-34755 + 2.2.0-preview1-34755 15.6.1 4.7.49 2.0.3 From 196e3f109f0ae2a561ba1024bce5b1567e252838 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 22 Jul 2018 16:34:01 -0700 Subject: [PATCH 121/316] React to Routing branding This is a reaction PR for the branding changes in progress in Routing. This can be merged after the changes to in to Routing. --- build/dependencies.props | 144 +++++++++--------- .../MvcCoreServiceCollectionExtensions.cs | 2 +- .../Internal/MvcEndpointDataSource.cs | 2 +- ...UrlHelper.cs => GlobalRoutingUrlHelper.cs} | 10 +- .../Routing/UrlHelperFactory.cs | 4 +- ...rTest.cs => GlobalRoutingUrlHelperTest.cs} | 6 +- ...=> ConsumesAttributeGlobalRoutingTests.cs} | 4 +- ...spatchingTests.cs => GlobalRoutingTest.cs} | 10 +- ...cs => RequestServicesGlobalRoutingTest.cs} | 4 +- ...est.cs => VersioningGlobalRoutingTests.cs} | 4 +- ...atching.cs => StartupWithGlobalRouting.cs} | 6 +- ...atching.cs => StartupWithGlobalRouting.cs} | 15 +- ...atching.cs => StartupWithGlobalRouting.cs} | 6 +- 13 files changed, 103 insertions(+), 114 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Core/Routing/{DispatcherUrlHelper.cs => GlobalRoutingUrlHelper.cs} (93%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/{DispatcherUrlHelperTest.cs => GlobalRoutingUrlHelperTest.cs} (97%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{ConsumesAttributeDispatchingTests.cs => ConsumesAttributeGlobalRoutingTests.cs} (54%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{DispatchingTests.cs => GlobalRoutingTest.cs} (77%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{VersioningDispatchingTests.cs => RequestServicesGlobalRoutingTest.cs} (55%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{RequestServicesDispatchingTest.cs => VersioningGlobalRoutingTests.cs} (55%) rename test/WebSites/BasicWebSite/{StartupWithDispatching.cs => StartupWithGlobalRouting.cs} (91%) rename test/WebSites/RoutingWebSite/{StartupWithDispatching.cs => StartupWithGlobalRouting.cs} (77%) rename test/WebSites/VersioningWebSite/{StartupWithDispatching.cs => StartupWithGlobalRouting.cs} (89%) diff --git a/build/dependencies.props b/build/dependencies.props index 12a0121bad..9c0bc2d198 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 2.2.0-preview1-17099 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 1.7.0 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 2.1.0 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34755 - 2.2.0-preview1-34755 + 2.2.0-preview1-34758 + 2.2.0-preview1-34758 15.6.1 4.7.49 2.0.3 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 2f48bbedb0..3d50285d76 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -261,7 +261,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddTransient(); // Many per app // - // Dispatching + // Global Routing / Endpoints // services.TryAddEnumerable( ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index c8738bb20e..f725da108b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // up the endpoints too. // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. - // This is for scenarios dealing with migrating existing Routing based code to Dispatcher world. + // This is for scenarios dealing with migrating existing Router based code to Global Routing world. var conventionalRouteOrder = 0; // Check each of the conventional templates to see if the action would be reachable diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs similarity index 93% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs index 8b0efc4c38..e77274c3cd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/DispatcherUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs @@ -12,14 +12,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// An implementation of that uses to build URLs /// for ASP.NET MVC within an application. /// - internal class DispatcherUrlHelper : UrlHelperBase + internal class GlobalRoutingUrlHelper : UrlHelperBase { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly LinkGenerator _linkGenerator; private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// - /// Initializes a new instance of the class using the specified + /// Initializes a new instance of the class using the specified /// . /// /// The for the current request. @@ -28,11 +28,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The used to generate the link. /// The . - public DispatcherUrlHelper( + public GlobalRoutingUrlHelper( ActionContext actionContext, IEndpointFinder routeValuesBasedEndpointFinder, LinkGenerator linkGenerator, - ILogger logger) + ILogger logger) : base(actionContext) { if (linkGenerator == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index 9d464aafa3..6db7f35638 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -53,9 +53,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing var services = httpContext.RequestServices; var linkGenerator = services.GetRequiredService(); var routeValuesBasedEndpointFinder = services.GetRequiredService>(); - var logger = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); - urlHelper = new DispatcherUrlHelper( + urlHelper = new GlobalRoutingUrlHelper( context, routeValuesBasedEndpointFinder, linkGenerator, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs similarity index 97% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs index b069feabc6..78c7551c43 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/DispatcherUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing { - public class DispatcherUrlHelperTest : UrlHelperTestBase + public class GlobalRoutingUrlHelperTest : UrlHelperTestBase { protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var urlHelperFactory = httpContext.RequestServices.GetRequiredService(); var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); - Assert.IsType(urlHelper); + Assert.IsType(urlHelper); return urlHelper; } @@ -136,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } var services = GetCommonServices(); - services.AddDispatcher(); + services.AddRouting(); services.TryAddEnumerable( ServiceDescriptor.Singleton(new DefaultEndpointDataSource(endpoints))); services.TryAddSingleton(); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs similarity index 54% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs index ced707f674..889e77e80b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeDispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs @@ -3,9 +3,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class ConsumesAttributeDispatchingTests : ConsumesAttributeTestsBase + public class ConsumesAttributeGlobalRoutingTests : ConsumesAttributeTestsBase { - public ConsumesAttributeDispatchingTests(MvcTestFixture fixture) + public ConsumesAttributeGlobalRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs similarity index 77% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index 0cee884762..1ab9950d68 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -4,27 +4,25 @@ using System; using System.Net; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Routing; using Newtonsoft.Json; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class DispatchingTests : RoutingTestsBase + public class GlobalRoutingTest : RoutingTestsBase { - public DispatchingTests(MvcTestFixture fixture) + public GlobalRoutingTest(MvcTestFixture fixture) : base(fixture) { } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { return Task.CompletedTask; } - [Fact(Skip = "Link generation issue in dispatching. Need to fix - https://github.com/aspnet/Routing/issues/590")] + [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task ConventionalRoutedAction_InArea_StaysInArea() { return Task.CompletedTask; diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs similarity index 55% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs index ff0c8e700c..0c1471df03 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningDispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs @@ -3,9 +3,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class VersioningDispatchingTests : VersioningTestsBase + public class RequestServicesGlobalRoutingTest : RequestServicesTestBase { - public VersioningDispatchingTests(MvcTestFixture fixture) + public RequestServicesGlobalRoutingTest(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs similarity index 55% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs index f725d9ed92..8c022a076c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesDispatchingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs @@ -3,9 +3,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class RequestServicesDispatchingTest : RequestServicesTestBase + public class VersioningGlobalRoutingTests : VersioningTestsBase { - public RequestServicesDispatchingTest(MvcTestFixture fixture) + public VersioningGlobalRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/WebSites/BasicWebSite/StartupWithDispatching.cs b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs similarity index 91% rename from test/WebSites/BasicWebSite/StartupWithDispatching.cs rename to test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs index eb122b7290..037a5312f8 100644 --- a/test/WebSites/BasicWebSite/StartupWithDispatching.cs +++ b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs @@ -7,12 +7,12 @@ using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite { - public class StartupWithDispatching + public class StartupWithGlobalRouting { // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddDispatcher(); + services.AddRouting(); services.AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Latest) @@ -29,7 +29,7 @@ namespace BasicWebSite // Initializes the RequestId service for each request app.UseMiddleware(); - app.UseDispatcher(); + app.UseGlobalRouting(); app.UseMvcWithEndpoint(routes => { diff --git a/test/WebSites/RoutingWebSite/StartupWithDispatching.cs b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs similarity index 77% rename from test/WebSites/RoutingWebSite/StartupWithDispatching.cs rename to test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs index 670ec5497d..abc0abf4fb 100644 --- a/test/WebSites/RoutingWebSite/StartupWithDispatching.cs +++ b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs @@ -1,27 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.IO; -using System.Text; using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; namespace RoutingWebSite { - public class StartupWithDispatching + public class StartupWithGlobalRouting { // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddDispatcher(); + services.AddRouting(); services.AddMvc(); @@ -31,7 +22,7 @@ namespace RoutingWebSite public void Configure(IApplicationBuilder app) { - app.UseDispatcher(); + app.UseGlobalRouting(); app.UseMvcWithEndpoint(routes => { diff --git a/test/WebSites/VersioningWebSite/StartupWithDispatching.cs b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs similarity index 89% rename from test/WebSites/VersioningWebSite/StartupWithDispatching.cs rename to test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs index 164f86550b..c7ea9739f7 100644 --- a/test/WebSites/VersioningWebSite/StartupWithDispatching.cs +++ b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs @@ -9,11 +9,11 @@ using Microsoft.Extensions.DependencyInjection; namespace VersioningWebSite { - public class StartupWithDispatching + public class StartupWithGlobalRouting { public void ConfigureServices(IServiceCollection services) { - services.AddDispatcher(); + services.AddRouting(); // Add MVC services to the services container services.AddMvc(); @@ -24,7 +24,7 @@ namespace VersioningWebSite public void Configure(IApplicationBuilder app) { - app.UseDispatcher(); + app.UseGlobalRouting(); app.UseMvcWithEndpoint(endpoints => { From 52c1e942c66da065f93e90e9e83ed50afc497607 Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Fri, 22 Jun 2018 20:31:00 +0200 Subject: [PATCH 122/316] Added UseCamelCasing and UseMemberCasing extension methods to MvcJsonOptions (#7256) --- .../MvcJsonOptionsExtensions.cs | 88 ++++++ .../Properties/AssemblyInfo.cs | 6 + .../Properties/Resources.Designer.cs | 58 ++++ .../Resources.resx | 126 +++++++++ .../MvcJsonOptionsExtensionsTests.cs | 264 ++++++++++++++++++ 5 files changed, 542 insertions(+) create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs new file mode 100644 index 0000000000..876325d281 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; + +namespace Microsoft.Extensions.DependencyInjection +{ + public static class MvcJsonOptionsExtensions + { + /// + /// Configures the casing behavior of JSON serialization to use camel case for property names, + /// and optionally for dynamic types and dictionary keys. + /// + /// + /// This method modifies . + /// + /// + /// If true will camel case dictionary keys and properties of dynamic objects. + /// with camel case settings. + public static MvcJsonOptions UseCamelCasing(this MvcJsonOptions options, bool processDictionaryKeys) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.SerializerSettings.ContractResolver is DefaultContractResolver resolver) + { + resolver.NamingStrategy = new CamelCaseNamingStrategy + { + ProcessDictionaryKeys = processDictionaryKeys + }; + } + else + { + if (options.SerializerSettings.ContractResolver == null) + { + throw new InvalidOperationException(Resources.FormatContractResolverCannotBeNull(nameof(JsonSerializerSettings.ContractResolver))); + } + + var contractResolverName = options.SerializerSettings.ContractResolver.GetType().Name; + throw new InvalidOperationException( + Resources.FormatInvalidContractResolverForJsonCasingConfiguration(contractResolverName, nameof(DefaultContractResolver))); + } + + return options; + } + + /// + /// Configures the casing behavior of JSON serialization to use the member's casing for property names, + /// properties of dynamic types, and dictionary keys. + /// + /// + /// This method modifies . + /// + /// + /// with member casing settings. + public static MvcJsonOptions UseMemberCasing(this MvcJsonOptions options) + { + if (options == null) + { + throw new ArgumentNullException(nameof(options)); + } + + if (options.SerializerSettings.ContractResolver is DefaultContractResolver resolver) + { + resolver.NamingStrategy = new DefaultNamingStrategy(); + } + else + { + if (options.SerializerSettings.ContractResolver == null) + { + throw new InvalidOperationException(Resources.FormatContractResolverCannotBeNull(nameof(JsonSerializerSettings.ContractResolver))); + } + + var contractResolverName = options.SerializerSettings.ContractResolver.GetType().Name; + throw new InvalidOperationException( + Resources.FormatInvalidContractResolverForJsonCasingConfiguration(contractResolverName, nameof(DefaultContractResolver))); + } + + return options; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..7982959233 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Json.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..b552db2345 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs @@ -0,0 +1,58 @@ +// +namespace Microsoft.AspNetCore.Mvc.Formatters.Json +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.AspNetCore.Mvc.Formatters.Json.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// {0} cannot be null. + /// + internal static string ContractResolverCannotBeNull + { + get => GetString("ContractResolverCannotBeNull"); + } + + /// + /// {0} cannot be null. + /// + internal static string FormatContractResolverCannotBeNull(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ContractResolverCannotBeNull"), p0); + + /// + /// Cannot configure JSON casing behavior on '{0}' contract resolver. The supported contract resolver is {1}. + /// + internal static string InvalidContractResolverForJsonCasingConfiguration + { + get => GetString("InvalidContractResolverForJsonCasingConfiguration"); + } + + /// + /// Cannot configure JSON casing behavior on '{0}' contract resolver. The supported contract resolver is {1}. + /// + internal static string FormatInvalidContractResolverForJsonCasingConfiguration(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidContractResolverForJsonCasingConfiguration"), p0, p1); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx new file mode 100644 index 0000000000..729545d687 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + {0} cannot be null. + + + Cannot configure JSON casing behavior on '{0}' contract resolver. The supported contract resolver is {1}. + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs new file mode 100644 index 0000000000..b3370c312a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs @@ -0,0 +1,264 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters.Json; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Xunit; + +namespace Microsoft.Extensions.DependencyInjection +{ + public class MvcJsonOptionsExtensionsTests + { + [Fact] + public void UseCamelCasing_WillSet_CamelCasingStrategy_NameStrategy() + { + // Arrange + var options = new MvcJsonOptions(); + options.SerializerSettings.ContractResolver = new DefaultContractResolver() + { + NamingStrategy = new DefaultNamingStrategy() + }; + var expected = typeof(CamelCaseNamingStrategy); + + // Act + options.UseCamelCasing(processDictionaryKeys: true); + var resolver = options.SerializerSettings.ContractResolver as DefaultContractResolver; + var actual = resolver.NamingStrategy; + + // Assert + Assert.IsType(expected, actual); + } + + [Fact] + public void UseCamelCasing_WillNot_OverrideSpecifiedNames() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var annotatedFoo = new AnnotatedFoo() + { + HelloWorld = "Hello" + }; + var expected = "{\"HELLO-WORLD\":\"Hello\"}"; + + // Act + var actual = SerializeToJson(options, value: annotatedFoo); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_WillChange_PropertyNames() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var foo = new { TestName = "TestFoo", TestValue = 10 }; + var expected = "{\"testName\":\"TestFoo\",\"testValue\":10}"; + + // Act + var actual = SerializeToJson(options, value: foo); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_WillChangeFirstPartBeforeSeparator_InPropertyName() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var foo = new { TestFoo_TestValue = "Test" }; + var expected = "{\"testFoo_TestValue\":\"Test\"}"; + + // Act + var actual = SerializeToJson(options, value: foo); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_ProcessDictionaryKeys_WillChange_DictionaryKeys_IfTrue() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var dictionary = new Dictionary + { + ["HelloWorld"] = 1, + ["HELLOWORLD"] = 2 + }; + var expected = "{\"helloWorld\":1,\"helloworld\":2}"; + + // Act + var actual = SerializeToJson(options, value: dictionary); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_ProcessDictionaryKeys_WillChangeFirstPartBeforeSeparator_InDictionaryKey_IfTrue() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var dictionary = new Dictionary() + { + ["HelloWorld_HelloWorld"] = 1 + }; + + var expected = "{\"helloWorld_HelloWorld\":1}"; + + // Act + var actual = SerializeToJson(options, value: dictionary); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_ProcessDictionaryKeys_WillNotChangeDictionaryKeys_IfFalse() + { + // Arrange + var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: false); + var dictionary = new Dictionary + { + ["HelloWorld"] = 1, + ["HELLO-WORLD"] = 2 + }; + var expected = "{\"HelloWorld\":1,\"HELLO-WORLD\":2}"; + + // Act + var actual = SerializeToJson(options, value: dictionary); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseMemberCasing_WillNotChange_OverrideSpecifiedNames() + { + // Arrange + var options = new MvcJsonOptions().UseMemberCasing(); + var annotatedFoo = new AnnotatedFoo() + { + HelloWorld = "Hello" + }; + var expected = "{\"HELLO-WORLD\":\"Hello\"}"; + + // Act + var actual = SerializeToJson(options, value: annotatedFoo); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseMemberCasing_WillSet_DefaultNamingStrategy_AsNamingStrategy() + { + // Arrange + var options = new MvcJsonOptions(); + options.SerializerSettings.ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + }; + var expected = typeof(DefaultNamingStrategy); + + // Act + options.UseMemberCasing(); + var resolver = options.SerializerSettings.ContractResolver as DefaultContractResolver; + var actual = resolver.NamingStrategy; + + // Assert + Assert.IsType(expected, actual); + } + + [Fact] + public void UseMemberCasing_WillNotChange_PropertyNames() + { + // Arrange + var options = new MvcJsonOptions().UseMemberCasing(); + var foo = new { fooName = "Test", FooValue = "Value"}; + var expected = "{\"fooName\":\"Test\",\"FooValue\":\"Value\"}"; + + // Act + var actual = SerializeToJson(options, value: foo); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseMemberCasing_WillNotChange_DictionaryKeys() + { + // Arrange + var options = new MvcJsonOptions().UseMemberCasing(); + var dictionary = new Dictionary() + { + ["HelloWorld"] = 1, + ["helloWorld"] = 2, + ["HELLO-WORLD"] = 3 + }; + var expected = "{\"HelloWorld\":1,\"helloWorld\":2,\"HELLO-WORLD\":3}"; + + // Act + var actual = SerializeToJson(options, value: dictionary); + + // Assert + Assert.Equal(expected, actual); + } + + [Fact] + public void UseCamelCasing_WillThrow_IfContractResolver_IsNot_DefaultContractResolver() + { + // Arrange + var options = new MvcJsonOptions(); + options.SerializerSettings.ContractResolver = new FooContractResolver(); + var expectedMessage = Resources.FormatInvalidContractResolverForJsonCasingConfiguration(nameof(FooContractResolver), nameof(DefaultContractResolver)); + + // Act & Assert + var exception = Assert.Throws( + () => options.UseCamelCasing(processDictionaryKeys: false)); + Assert.Equal(expectedMessage, actual: exception.Message); + } + + [Fact] + public void UseMemberCasing_WillThrow_IfContractResolver_IsNot_DefaultContractResolver() + { + // Arrange + var options = new MvcJsonOptions(); + options.SerializerSettings.ContractResolver = new FooContractResolver(); + var expectedMessage = Resources.FormatInvalidContractResolverForJsonCasingConfiguration(nameof(FooContractResolver), nameof(DefaultContractResolver)); + + // Act & Assert + var exception = Assert.Throws( + () => options.UseMemberCasing()); + Assert.Equal(expectedMessage, actual: exception.Message); + } + + private static string SerializeToJson(MvcJsonOptions options, object value) + { + return JsonConvert.SerializeObject( + value: value, + formatting: Formatting.None, + settings: options.SerializerSettings); + } + + private class AnnotatedFoo + { + [JsonProperty("HELLO-WORLD")] + public string HelloWorld { get; set; } + } + + private class FooContractResolver : IContractResolver + { + public JsonContract ResolveContract(Type type) + { + throw new NotImplementedException(); + } + } + } +} From 3df34dbbfef274c0b89d02e4b681771dc8e8e5ce Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 24 Jul 2018 05:20:53 -0700 Subject: [PATCH 123/316] React to Routing repo's LinkGenerator api changes --- .../Routing/GlobalRoutingUrlHelper.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs index e77274c3cd..13dd697fce 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs @@ -94,10 +94,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing }); var successfullyGeneratedLink = _linkGenerator.TryGetLink( - ActionContext.HttpContext, - endpoints, - valuesDictionary, - AmbientValues, + new LinkGeneratorContext + { + HttpContext = ActionContext.HttpContext, + Endpoints = endpoints, + ExplicitValues = valuesDictionary, + AmbientValues = AmbientValues + }, out var link); if (!successfullyGeneratedLink) @@ -129,10 +132,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing }); var successfullyGeneratedLink = _linkGenerator.TryGetLink( - ActionContext.HttpContext, - endpoints, - valuesDictionary, - AmbientValues, + new LinkGeneratorContext + { + HttpContext = ActionContext.HttpContext, + Endpoints = endpoints, + ExplicitValues = valuesDictionary, + AmbientValues = AmbientValues + }, out var link); if (!successfullyGeneratedLink) From 9b217892ab9bc7b24ccedb092405c1c468f0620c Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 24 Jul 2018 05:21:37 -0700 Subject: [PATCH 124/316] Using routing feature branch versions to prevent cross repo breaking changes --- build/dependencies.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 9c0bc2d198..e478d41d3f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -39,7 +39,7 @@ 2.2.0-preview1-34758 2.2.0-preview1-34758 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-a-preview1-routeoptions-linkgenerator-16726 2.2.0-preview1-34758 2.2.0-preview1-34758 2.2.0-preview1-34758 @@ -48,8 +48,8 @@ 2.2.0-preview1-34758 2.2.0-preview1-34758 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-a-preview1-routeoptions-linkgenerator-16775 + 2.2.0-a-preview1-routeoptions-linkgenerator-16775 2.2.0-preview1-34758 2.2.0-preview1-34758 2.2.0-preview1-34758 From 5580928209471a4d004082ffe495f59f65d7f075 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 24 Jul 2018 09:18:08 -0700 Subject: [PATCH 125/316] Upgraded dependencies.props --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index e478d41d3f..889ffe96a4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 2.2.0-preview1-17099 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-a-preview1-routeoptions-linkgenerator-16726 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-a-preview1-routeoptions-linkgenerator-16775 - 2.2.0-a-preview1-routeoptions-linkgenerator-16775 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 1.7.0 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 2.1.0 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34758 - 2.2.0-preview1-34758 + 2.2.0-preview1-34773 + 2.2.0-preview1-34773 15.6.1 4.7.49 2.0.3 From f850e31bfbfc03bba32df296101192e33cf4b4c0 Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Tue, 24 Jul 2018 10:54:26 -0700 Subject: [PATCH 126/316] Update CONTRIBUTING.md --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 64ff041d5c..eac4268e4c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ Contributing ====== -Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/dev/CONTRIBUTING.md) in the Home repo. +Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. From 630aeade07b101e7e45b3140868c157beb2d74ec Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Mon, 23 Jul 2018 14:01:11 -0700 Subject: [PATCH 127/316] Added tests related to generating urls with route name --- .../Routing/GlobalRoutingUrlHelperTest.cs | 182 ++++++++++++++++-- .../Routing/UrlHelperTest.cs | 5 + .../Routing/UrlHelperTestBase.cs | 31 ++- 3 files changed, 201 insertions(+), 17 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs index 78c7551c43..b207c8dda4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs @@ -16,6 +16,83 @@ namespace Microsoft.AspNetCore.Mvc.Routing { public class GlobalRoutingUrlHelperTest : UrlHelperTestBase { + [Fact] + public void RouteUrl_WithRouteName_GeneratesUrl_UsingDefaults() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Equal("/" + endpoint2.RoutePattern.RawText, url); + } + + [Fact] + public void RouteUrl_WithRouteName_UsesAmbientValues() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + urlHelper.ActionContext.RouteData.Values["id"] = "500"; + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Equal("/api/orders/500", url); + } + + [Fact] + public void RouteUrl_WithRouteName_UsesSuppliedValue_OverridingAmbientValue() + { + // Arrange + var endpoint1 = CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + routeName: "OrdersApi"); + var endpoint2 = CreateEndpoint( + "api/orders", + defaults: new { controller = "Orders", action = "GetAll" }, + requiredValues: new { controller = "Orders", action = "GetAll" }, + routeName: "OrdersApi"); + var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); + urlHelper.ActionContext.RouteData.Values["id"] = "500"; + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { id = "10" }); + + // Assert + Assert.Equal("/api/orders/10", url); + } + protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); @@ -84,6 +161,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing return CreateUrlHelper(actionContext); } + private IUrlHelper CreateUrlHelper(IEnumerable endpoints, ActionContext actionContext = null) + { + var serviceProvider = CreateServices(endpoints); + var httpContext = CreateHttpContext(serviceProvider, null, null, "http"); + actionContext = actionContext ?? CreateActionContext(httpContext); + return CreateUrlHelper(actionContext); + } + private IUrlHelper CreateUrlHelper( IEnumerable endpoints, string appRoot, @@ -99,33 +184,98 @@ namespace Microsoft.AspNetCore.Mvc.Routing private List GetDefaultEndpoints() { var endpoints = new List(); - endpoints.Add(CreateEndpoint(null, "home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 1)); - endpoints.Add(CreateEndpoint(null, "home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 2)); - endpoints.Add(CreateEndpoint(null, "home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 3)); - endpoints.Add(CreateEndpoint(null, "home2/contact/{id}", new { id = "defaultid", controller = "home2", action = "contact" }, 4)); - endpoints.Add(CreateEndpoint(null, "home3/contact/{id}", new { id = "defaultid", controller = "home3", action = "contact" }, 5)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/newaction/{id}", new { id = "defaultid", controller = "home", action = "newaction" }, 6)); - endpoints.Add(CreateEndpoint("namedroute", "named/home2/newaction/{id}", new { id = "defaultid", controller = "home2", action = "newaction" }, 7)); - endpoints.Add(CreateEndpoint("namedroute", "named/home/contact/{id}", new { id = "defaultid", controller = "home", action = "contact" }, 8)); - endpoints.Add(CreateEndpoint("MyRouteName", "any/url", new { }, 9)); + endpoints.Add( + CreateEndpoint( + "home/newaction/{id}", + defaults: new { id = "defaultid", controller = "home", action = "newaction" }, + requiredValues: new { controller = "home", action = "newaction" }, + order: 1)); + endpoints.Add( + CreateEndpoint( + "home/contact/{id}", + defaults: new { id = "defaultid", controller = "home", action = "contact" }, + requiredValues: new { controller = "home", action = "contact" }, + order: 2)); + endpoints.Add( + CreateEndpoint( + "home2/newaction/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "newaction" }, + requiredValues: new { controller = "home2", action = "newaction" }, + order: 3)); + endpoints.Add( + CreateEndpoint( + "home2/contact/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "contact" }, + requiredValues: new { controller = "home2", action = "contact" }, + order: 4)); + endpoints.Add( + CreateEndpoint( + "home3/contact/{id}", + defaults: new { id = "defaultid", controller = "home3", action = "contact" }, + requiredValues: new { controller = "home3", action = "contact" }, + order: 5)); + endpoints.Add( + CreateEndpoint( + "named/home/newaction/{id}", + defaults: new { id = "defaultid", controller = "home", action = "newaction" }, + requiredValues: new { controller = "home", action = "newaction" }, + order: 6, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "named/home2/newaction/{id}", + defaults: new { id = "defaultid", controller = "home2", action = "newaction" }, + requiredValues: new { controller = "home2", action = "newaction" }, + order: 7, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "named/home/contact/{id}", + defaults: new { id = "defaultid", controller = "home", action = "contact" }, + requiredValues: new { controller = "home", action = "contact" }, + order: 8, + routeName: "namedroute")); + endpoints.Add( + CreateEndpoint( + "any/url", + defaults: new { }, + requiredValues: new { }, + order: 9, + routeName: "MyRouteName")); + endpoints.Add( + CreateEndpoint( + "api/orders/{id}", + defaults: new { controller = "Orders", action = "GetById" }, + requiredValues: new { controller = "Orders", action = "GetById" }, + order: 10, + routeName: "OrdersApi")); return endpoints; } - private MatcherEndpoint CreateEndpoint(string routeName, string template, object defaults, int order) + private MatcherEndpoint CreateEndpoint( + string template, + object defaults = null, + object requiredValues = null, + int order = 0, + string routeName = null, + EndpointMetadataCollection metadataCollection = null) { - var metadata = EndpointMetadataCollection.Empty; - if (!string.IsNullOrEmpty(routeName)) + if (metadataCollection == null) { - metadata = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + metadataCollection = EndpointMetadataCollection.Empty; + if (!string.IsNullOrEmpty(routeName)) + { + metadataCollection = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); + } } return new MatcherEndpoint( next => (httpContext) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(), + new RouteValueDictionary(requiredValues), order, - metadata, - "DisplayName"); + metadataCollection, + null); } private IServiceProvider CreateServices(IEnumerable endpoints) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs index 63c88896ab..2d7e4a0f15 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs @@ -101,6 +101,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing .Returns(context => null); routeBuilder.DefaultHandler = target.Object; + routeBuilder.MapRoute( + "OrdersApi", + "api/orders/{id}", + new RouteValueDictionary(new { controller = "Orders", action = "GetById" })); + routeBuilder.MapRoute( string.Empty, "{controller}/{action}/{id}", diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs index 300efe8c4a..d6dfe08822 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs @@ -457,6 +457,36 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("https://pingüino/app/named/home2/newaction/someid", url); } + [Fact] + public void RouteUrl_GeneratesUrl_WithRouteName_UsingDefaultValues_WhenExplicitOrAmbientValues_NotPresent() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { id = "500" }); + + // Assert + Assert.Equal("/app/api/orders/500", url); + } + + [Fact] + public void RouteUrl_WithRouteName_DoesNotGenerateUrl_WhenRequiredValueForParameter_NotPresent() + { + // Arrange + var urlHelper = CreateUrlHelperWithDefaultRoutes(); + + // Act + var url = urlHelper.RouteUrl( + routeName: "OrdersApi", + values: new { }); + + // Assert + Assert.Null(url); + } + [Fact] public void RouteUrlWithRouteNameAndDictionary() { @@ -922,7 +952,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("/b/Store/Checkout", url); } - protected abstract IServiceProvider CreateServices(); protected abstract IUrlHelper CreateUrlHelper(ActionContext actionContext); From c08504b08ab404e5c58699d94fc092e9e2e7f87a Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 25 Jul 2018 14:30:51 +1200 Subject: [PATCH 128/316] MVC startup experience (#8131) --- .../MvcApplicationBuilderExtensions.cs | 92 +++++++++++++++++-- .../Builder/MvcEndpointInfo.cs | 9 +- ...MvcOptionsConfigureCompatibilityOptions.cs | 5 + .../Internal/MvcEndpointDataSource.cs | 15 +-- .../Internal/NullRouter.cs | 27 ++++++ .../MvcOptions.cs | 10 ++ .../ConsumesAttributeGlobalRoutingTests.cs | 20 ++++ .../ConsumesAttributeTests.cs | 20 ++++ .../ConsumesAttributeTestsBase.cs | 3 + .../GlobalRoutingTest.cs | 15 +++ .../RequestServicesGlobalRoutingTest.cs | 20 ++++ .../RequestServicesTest.cs | 20 ++++ .../RequestServicesTestBase.cs | 3 + .../RoutingTests.cs | 15 +++ .../RoutingTestsBase.cs | 3 + .../VersioningGlobalRoutingTests.cs | 20 ++++ .../VersioningTests.cs | 20 ++++ .../VersioningTestsBase.cs | 3 + .../CompatibilitySwitchIntegrationTest.cs | 28 ++++++ .../Controllers/RoutingController.cs | 17 ++++ test/WebSites/BasicWebSite/Startup.cs | 3 + .../BasicWebSite/StartupWithGlobalRouting.cs | 6 +- .../Controllers/RoutingController.cs | 17 ++++ .../StartupWithGlobalRouting.cs | 15 ++- .../Controllers/RoutingController.cs | 17 ++++ .../StartupWithGlobalRouting.cs | 14 +-- 26 files changed, 391 insertions(+), 46 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs create mode 100644 test/WebSites/BasicWebSite/Controllers/RoutingController.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/RoutingController.cs create mode 100644 test/WebSites/VersioningWebSite/Controllers/RoutingController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 74e5f8e5ec..0b190b0231 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -4,10 +4,12 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Builder { @@ -16,6 +18,9 @@ namespace Microsoft.AspNetCore.Builder /// public static class MvcApplicationBuilderExtensions { + // Property key set in routing package by UseGlobalRouting to indicate middleware is registered + private const string GlobalRoutingRegisteredKey = "__GlobalRoutingMiddlewareRegistered"; + /// /// Adds MVC to the request execution pipeline. /// @@ -79,16 +84,91 @@ namespace Microsoft.AspNetCore.Builder VerifyMvcIsRegistered(app); - var routes = new RouteBuilder(app) + var options = app.ApplicationServices.GetRequiredService>(); + + if (options.Value.EnableGlobalRouting) { - DefaultHandler = app.ApplicationServices.GetRequiredService(), - }; + var mvcEndpointDataSource = app.ApplicationServices + .GetRequiredService>() + .OfType() + .First(); + var constraintResolver = app.ApplicationServices + .GetRequiredService(); - configureRoutes(routes); + var endpointRouteBuilder = new EndpointRouteBuilder(app); - routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); + configureRoutes(endpointRouteBuilder); - return app.UseRouter(routes.Build()); + foreach (var router in endpointRouteBuilder.Routes) + { + // Only accept Microsoft.AspNetCore.Routing.Route when converting to endpoint + // Sub-types could have additional customization that we can't knowingly convert + if (router is Route route && router.GetType() == typeof(Route)) + { + var endpointInfo = new MvcEndpointInfo( + route.Name, + route.RouteTemplate, + route.Defaults, + route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value), + route.DataTokens, + constraintResolver); + + mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo); + } + else + { + throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Global Routing."); + } + } + + if (!app.Properties.TryGetValue(GlobalRoutingRegisteredKey, out _)) + { + // Matching middleware has not been registered yet + // For back-compat register middleware so an endpoint is matched and then immediately used + app.UseGlobalRouting(); + } + + return app.UseEndpoint(); + } + else + { + var routes = new RouteBuilder(app) + { + DefaultHandler = app.ApplicationServices.GetRequiredService(), + }; + + configureRoutes(routes); + + routes.Routes.Insert(0, AttributeRouting.CreateAttributeMegaRoute(app.ApplicationServices)); + + return app.UseRouter(routes.Build()); + } + } + + private class EndpointRouteBuilder : IRouteBuilder + { + public EndpointRouteBuilder(IApplicationBuilder applicationBuilder) + { + ApplicationBuilder = applicationBuilder; + Routes = new List(); + DefaultHandler = NullRouter.Instance; + } + + public IApplicationBuilder ApplicationBuilder { get; } + + public IRouter DefaultHandler { get; set; } + + public IServiceProvider ServiceProvider + { + get { return ApplicationBuilder.ApplicationServices; } + } + + public IList Routes { get; } + + public IRouter Build() + { + throw new NotSupportedException(); + } } public static IApplicationBuilder UseMvcWithEndpoint( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index bb6a9df95f..ba59c34b44 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -94,10 +94,13 @@ namespace Microsoft.AspNetCore.Builder { if (parameter.DefaultValue != null) { - if (result.ContainsKey(parameter.Name)) + if (result.TryGetValue(parameter.Name, out var value)) { - throw new InvalidOperationException( - string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + if (!object.Equals(value, parameter.DefaultValue)) + { + throw new InvalidOperationException( + string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + } } else { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index 71957b40cf..28fd0605e7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -32,6 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure values[nameof(MvcOptions.SuppressBindingUndefinedValueToEnumType)] = true; } + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(MvcOptions.EnableGlobalRouting)] = true; + } + return values; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index f725da108b..d6bc8c8f68 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // // REVIEW: This is really ugly if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) - && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, new DummyRouter(), routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return false; @@ -260,19 +260,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal return false; } - private class DummyRouter : IRouter - { - public VirtualPathData GetVirtualPath(VirtualPathContext context) - { - return null; - } - - public Task RouteAsync(RouteContext context) - { - return Task.CompletedTask; - } - } - private MatcherEndpoint CreateEndpoint( ActionDescriptor action, string routeName, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs new file mode 100644 index 0000000000..c1b800c5bd --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs @@ -0,0 +1,27 @@ +// 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.Threading.Tasks; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal class NullRouter : IRouter + { + public static IRouter Instance = new NullRouter(); + + private NullRouter() + { + } + + public VirtualPathData GetVirtualPath(VirtualPathContext context) + { + return null; + } + + public Task RouteAsync(RouteContext context) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index 72634a49fa..e87ddb2678 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc private readonly CompatibilitySwitch _allowValidatingTopLevelNodes; private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; + private readonly CompatibilitySwitch _enableGlobalRouting; private readonly ICompatibilitySwitch[] _switches; /// @@ -54,6 +55,7 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes = new CompatibilitySwitch(nameof(AllowValidatingTopLevelNodes)); _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); + _enableGlobalRouting = new CompatibilitySwitch(nameof(EnableGlobalRouting)); _switches = new ICompatibilitySwitch[] { @@ -62,9 +64,17 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes, _inputFormatterExceptionPolicy, _suppressBindingUndefinedValueToEnumType, + _enableGlobalRouting, }; } + // REVIEW: Add documentation + public bool EnableGlobalRouting + { + get => _enableGlobalRouting.Value; + set => _enableGlobalRouting.Value = value; + } + /// /// Gets or sets the flag which decides whether body model binding (for example, on an /// action method parameter with ) should treat empty diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs index 889e77e80b..1683864c17 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class ConsumesAttributeGlobalRoutingTests : ConsumesAttributeTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs index a8db21d806..d694aad1c4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class ConsumesAttributeTests : ConsumesAttributeTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index 9a414488b1..acc68febbc 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -28,6 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Fact] public async Task NoRequestContentType_SelectsActionWithoutConstraint() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index 1ab9950d68..a95147250d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -16,6 +16,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } + [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs index 0c1471df03..0ca18c250e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class RequestServicesGlobalRoutingTest : RequestServicesTestBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs index f62521bd6c..73c0e5af15 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class RequestServicesTest : RequestServicesTestBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs index 425bda5ea5..cc4e06ba74 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs @@ -26,6 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Theory] [InlineData("http://localhost/RequestScopedService/FromFilter")] [InlineData("http://localhost/RequestScopedService/FromView")] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index e4c3f088ea..a4fbb4ab58 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -17,6 +17,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } + [Fact] public async override Task RouteData_Routers_ConventionalRoute() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 20918254b9..f707655dce 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -28,6 +28,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Fact] public abstract Task RouteData_Routers_ConventionalRoute(); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs index 8c022a076c..af2147949f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class VersioningGlobalRoutingTests : VersioningTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.True(result); + } } } \ 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 8490fda117..40e14bdc57 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs @@ -1,6 +1,11 @@ // 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.Net; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public class VersioningTests : VersioningTestsBase @@ -9,5 +14,20 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests : base(fixture) { } + + [Fact] + public async override Task HasEndpointMatch() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Routing/HasEndpointMatch"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.False(result); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs index 46cfdbe4a8..152c08a799 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs @@ -25,6 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public abstract Task HasEndpointMatch(); + [Theory] [InlineData("1")] [InlineData("2")] diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 998343d7bb..78f8ddb4ae 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -39,6 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(razorPagesOptions.AllowAreas); + Assert.False(mvcOptions.EnableGlobalRouting); } [Fact] @@ -63,6 +64,32 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); + Assert.False(mvcOptions.EnableGlobalRouting); + } + + [Fact] + public void CompatibilitySwitches_Version_2_2() + { + // Arrange + var serviceCollection = new ServiceCollection(); + AddHostingServices(serviceCollection); + serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + + var services = serviceCollection.BuildServiceProvider(); + + // Act + var mvcOptions = services.GetRequiredService>().Value; + var jsonOptions = services.GetRequiredService>().Value; + var razorPagesOptions = services.GetRequiredService>().Value; + + // Assert + Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); + Assert.True(mvcOptions.AllowBindingHeaderValuesToNonStringModelTypes); + Assert.True(mvcOptions.SuppressBindingUndefinedValueToEnumType); + Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); + Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); + Assert.True(razorPagesOptions.AllowAreas); + Assert.True(mvcOptions.EnableGlobalRouting); } [Fact] @@ -87,6 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); + Assert.True(mvcOptions.EnableGlobalRouting); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..afe914f572 --- /dev/null +++ b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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; +using Microsoft.AspNetCore.Routing; + +namespace BasicWebSite +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 9e20cb447c..c6e406fb34 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -26,6 +26,9 @@ namespace BasicWebSite options.Conventions.Add(new ApplicationDescription("This is a basic website.")); // Filter that records a value in HttpContext.Items options.Filters.Add(new TraceResourceFilter()); + + // Remove when all URL generation tests are passing - https://github.com/aspnet/Routing/issues/590 + options.EnableGlobalRouting = false; }) .SetCompatibilityVersion(CompatibilityVersion.Latest) .AddXmlDataContractSerializerFormatters(); diff --git a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs index 037a5312f8..d3a540a86c 100644 --- a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs @@ -15,7 +15,7 @@ namespace BasicWebSite services.AddRouting(); services.AddMvc() - .SetCompatibilityVersion(CompatibilityVersion.Latest) + .SetCompatibilityVersion(CompatibilityVersion.Latest) // this compat version enables global routing .AddXmlDataContractSerializerFormatters(); services.ConfigureBaseWebSiteAuthPolicies(); @@ -31,9 +31,9 @@ namespace BasicWebSite app.UseGlobalRouting(); - app.UseMvcWithEndpoint(routes => + app.UseMvc(routes => { - routes.MapEndpoint( + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); diff --git a/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..354ff5539a --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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; +using Microsoft.AspNetCore.Routing; + +namespace RoutingWebSite +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs index abc0abf4fb..62a84f5ecb 100644 --- a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs @@ -12,9 +12,8 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddRouting(); - - services.AddMvc(); + services.AddMvc() + .AddMvcOptions(options => options.EnableGlobalRouting = true); services.AddScoped(); services.AddSingleton(); @@ -22,23 +21,21 @@ namespace RoutingWebSite public void Configure(IApplicationBuilder app) { - app.UseGlobalRouting(); - - app.UseMvcWithEndpoint(routes => + app.UseMvc(routes => { - routes.MapAreaEndpoint( + routes.MapAreaRoute( "flightRoute", "adminRoute", "{area:exists}/{controller}/{action}", new { controller = "Home", action = "Index" }, new { area = "Travel" }); - routes.MapEndpoint( + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); - routes.MapEndpoint( + routes.MapRoute( "RouteWithOptionalSegment", "{controller}/{action}/{path?}"); }); diff --git a/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs new file mode 100644 index 0000000000..aad2099d1d --- /dev/null +++ b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs @@ -0,0 +1,17 @@ +// 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; +using Microsoft.AspNetCore.Routing; + +namespace VersioningWebSite +{ + public class RoutingController : Controller + { + public ActionResult HasEndpointMatch() + { + var endpointFeature = HttpContext.Features.Get(); + return Json(endpointFeature?.Endpoint != null); + } + } +} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs index c7ea9739f7..249aefabd4 100644 --- a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs @@ -13,10 +13,9 @@ namespace VersioningWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddRouting(); - // Add MVC services to the services container - services.AddMvc(); + services.AddMvc() + .AddMvcOptions(options => options.EnableGlobalRouting = true); services.AddScoped(); services.AddSingleton(); @@ -24,14 +23,7 @@ namespace VersioningWebSite public void Configure(IApplicationBuilder app) { - app.UseGlobalRouting(); - - app.UseMvcWithEndpoint(endpoints => - { - endpoints.MapEndpoint( - name: "default", - template: "{controller=Home}/{action=Index}/{id?}"); - }); + app.UseMvcWithDefaultRoute(); } } } \ No newline at end of file From b71d5da05ee1c68ae43b081f1e49db16b2fc8496 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 24 Jul 2018 21:50:13 -0700 Subject: [PATCH 129/316] Fix test break due to 405 returned from routing --- build/dependencies.props | 144 +++++++++--------- .../GlobalRoutingTest.cs | 84 ++++++++++ .../RoutingTestsBase.cs | 8 +- 3 files changed, 160 insertions(+), 76 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 889ffe96a4..6b40e23716 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 2.2.0-preview1-17099 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 1.7.0 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 2.1.0 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34773 - 2.2.0-preview1-34773 + 2.2.0-preview1-34781 + 2.2.0-preview1-34781 15.6.1 4.7.49 2.0.3 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index a95147250d..574b7f8160 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -3,6 +3,7 @@ using System; using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -76,5 +77,88 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Array.Empty(), result.Routers); } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Fact] + public override async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/Travel/Flight/BuyTickets"); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Fact] + public override async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + { + // Arrange + var url = "http://localhost/api/v1/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Theory] + [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] + [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] + [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + public override async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + string url, + string method) + { + // Arrange + var expectedUrl = new Uri(url).AbsolutePath; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod(method), url)); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // Global routing exposes HTTP 405s for HTTP method mismatches + [Theory] + [InlineData("Post", "/Friends")] + [InlineData("Put", "/Friends")] + [InlineData("Patch", "/Friends")] + [InlineData("Options", "/Friends")] + [InlineData("Head", "/Friends")] + public override async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + string method, + string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), $"http://localhost{url}"); + + // Assert + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + + // These verbs don't match + [Theory] + [InlineData("/Bank/Deposit", "GET")] + [InlineData("/Bank/Deposit/5", "DELETE")] + [InlineData("/Bank/Withdraw/5", "GET")] + public override async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index f707655dce..a3d236d318 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("Patch", "/Friends")] [InlineData("Options", "/Friends")] [InlineData("Head", "/Friends")] - public async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( + public virtual async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( string method, string url) { @@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() { // Arrange var url = "http://localhost/api/v1/Maps"; @@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] - public async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( + public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( string url, string method) { @@ -1212,7 +1212,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("/Bank/Deposit", "GET")] [InlineData("/Bank/Deposit/5", "DELETE")] [InlineData("/Bank/Withdraw/5", "GET")] - public async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) + public virtual async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) { // Arrange var request = new HttpRequestMessage(new HttpMethod(verb), "http://localhost" + path); From bcd6e83591aaf9613ae990b3382a46c9005db5c0 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 25 Jul 2018 06:15:17 -0700 Subject: [PATCH 130/316] Upgraded dependencies.props --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 6b40e23716..e24177e98d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 2.2.0-preview1-17099 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 1.7.0 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 2.1.0 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34781 - 2.2.0-preview1-34781 + 2.2.0-preview1-34784 + 2.2.0-preview1-34784 15.6.1 4.7.49 2.0.3 From 10ce77b9ca8435aead7369ff854a50fffbf4d235 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 24 Jul 2018 14:59:51 -0700 Subject: [PATCH 131/316] Updated MvcEndpointDataSource to create endpoints with SuppressLinkGenerationMetadata --- .../Internal/MvcEndpointDataSource.cs | 27 ++++++++++++++----- .../Routing/GlobalRoutingUrlHelperTest.cs | 20 ++++++++++++++ 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index d6bc8c8f68..2a366fab18 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -120,7 +120,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal subTemplate, endpointInfo.Defaults, ++conventionalRouteOrder, - endpointInfo); + endpointInfo, + suppressLinkGeneration: false); endpoints.Add(subEndpoint); } @@ -145,7 +146,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal newTemplate, endpointInfo.Defaults, ++conventionalRouteOrder, - endpointInfo); + endpointInfo, + suppressLinkGeneration: false); endpoints.Add(endpoint); } } @@ -158,7 +160,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal action.AttributeRouteInfo.Template, nonInlineDefaults: null, action.AttributeRouteInfo.Order, - action.AttributeRouteInfo); + action.AttributeRouteInfo, + suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration); endpoints.Add(endpoint); } } @@ -266,7 +269,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal string template, object nonInlineDefaults, int order, - object source) + object source, + bool suppressLinkGeneration) { RequestDelegate invokerDelegate = (context) => { @@ -289,7 +293,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var defaults = new RouteValueDictionary(nonInlineDefaults); EnsureRequiredValuesInDefaults(action.RouteValues, defaults); - var metadataCollection = BuildEndpointMetadata(action, routeName, source); + var metadataCollection = BuildEndpointMetadata(action, routeName, source, suppressLinkGeneration); var endpoint = new MatcherEndpoint( next => invokerDelegate, RoutePatternFactory.Parse(template, defaults, constraints: null), @@ -301,7 +305,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal return endpoint; } - private static EndpointMetadataCollection BuildEndpointMetadata(ActionDescriptor action, string routeName, object source) + private static EndpointMetadataCollection BuildEndpointMetadata( + ActionDescriptor action, + string routeName, + object source, + bool suppressLinkGeneration) { var metadata = new List(); // REVIEW: Used for debugging. Consider removing before release @@ -341,6 +349,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + if (suppressLinkGeneration) + { + metadata.Add(new SuppressLinkGenerationMetadata()); + } + var metadataCollection = new EndpointMetadataCollection(metadata); return metadataCollection; } @@ -428,5 +441,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public string Name { get; } } + + private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs index b207c8dda4..4c59c5b104 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs @@ -93,6 +93,24 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal("/api/orders/10", url); } + [Fact] + public void RouteUrl_DoesNotGenerateLink_ToEndpointsWithSuppressLinkGeneration() + { + // Arrange + var endpoint = CreateEndpoint( + "Home/Index", + defaults: new { controller = "Home", action = "Index" }, + requiredValues: new { controller = "Home", action = "Index" }, + metadataCollection: new EndpointMetadataCollection(new[] { new SuppressLinkGenerationMetadata() })); + var urlHelper = CreateUrlHelper(new[] { endpoint }); + + // Act + var url = urlHelper.RouteUrl(new { controller = "Home", action = "Index" }); + + // Assert + Assert.Null(url); + } + protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); @@ -313,5 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public string Name { get; } } + + private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } From f31ab716eeebe19d7c3d7f053a05faa82a23c4f5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 26 Jul 2018 09:20:26 +1200 Subject: [PATCH 132/316] Change MvcEndpointDataSource to use GetChangeToken (#8137) --- .../Internal/MvcEndpointDataSource.cs | 2 +- .../Internal/MvcEndpointDataSourceTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 2a366fab18..dda3d9f47f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -396,7 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return new CompositeChangeToken(changeTokens); } - public override IChangeToken ChangeToken => GetCompositeChangeToken(); + public override IChangeToken GetChangeToken() => GetCompositeChangeToken(); public override IReadOnlyList Endpoints { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index fb0df12fd3..c49d9f0243 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void ChangeToken_MultipleChangeTokenProviders_ComposedResult() + public void GetChangeToken_MultipleChangeTokenProviders_ComposedResult() { // Arrange var featureCollection = new FeatureCollection(); @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal new[] { changeProvider1Mock.Object, changeProvider2Mock.Object }); // Act - var changeToken = dataSource.ChangeToken; + var changeToken = dataSource.GetChangeToken(); // Assert var compositeChangeToken = Assert.IsType(changeToken); From 498fa2d72f0fc031545e9900f274a8298257bd3a Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sat, 14 Jul 2018 15:45:12 -0700 Subject: [PATCH 133/316] Avoid `InvalidOperationException` when serializing `SerializableError` - #8055 - provide unique name (``) for XML elements that would otherwise be nameless nits: - remove now-useless Mono special case in updated test class - extend updated tests to involve square brackets as well as empty keys --- .../SerializableErrorWrapper.cs | 15 +++++- .../Internal/SerializableErrorWrapperTests.cs | 53 +++++++++++++------ 2 files changed, 52 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs index d247b2fab9..e8a28f576f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs @@ -14,6 +14,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml [XmlRoot("Error")] public sealed class SerializableErrorWrapper : IXmlSerializable, IUnwrappable { + // Element name used when ModelStateEntry's Key is empty. Dash in element name should avoid collisions with + // other ModelState entries because the character is not legal in an expression name. + private static readonly string EmptyKey = "MVC-Empty"; + // Note: XmlSerializer requires to have default constructor public SerializableErrorWrapper() { @@ -63,6 +67,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { var key = XmlConvert.DecodeName(reader.LocalName); var value = reader.ReadInnerXml(); + if (string.Equals(EmptyKey, key, StringComparison.Ordinal)) + { + key = string.Empty; + } SerializableError.Add(key, value); reader.MoveToContent(); @@ -81,6 +89,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { var key = keyValuePair.Key; var value = keyValuePair.Value; + if (string.IsNullOrEmpty(key)) + { + key = EmptyKey; + } + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); if (value != null) { @@ -102,4 +115,4 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml return SerializableError; } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs index 2a6cfaf47d..ec0175b8df 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs @@ -6,7 +6,6 @@ using System.Runtime.Serialization; using System.Text; using System.Xml; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Testing; using Xunit; namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal @@ -28,8 +27,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal public void WrappedSerializableErrorInstance_ReturnedFromProperty() { // Arrange - var serializableError = new SerializableError(); - serializableError.Add("key1", "key1-error"); + var serializableError = new SerializableError + { + { "key1", "key1-error" } + }; // Act var wrapper = new SerializableErrorWrapper(serializableError); @@ -57,7 +58,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal { // Arrange var serializableErrorXml = "" + - "Test Error 1 Test Error 2Test Error 3"; + "Test error 0" + + "Test Error 1 Test Error 2" + + "Test Error 3" + + "Test Error 4"; var serializer = new DataContractSerializer(typeof(SerializableErrorWrapper)); // Act @@ -66,8 +70,28 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal var errors = wrapper.SerializableError; // Assert - Assert.Equal("Test Error 1 Test Error 2", errors["key1"]); - Assert.Equal("Test Error 3", errors["key2"]); + Assert.Collection( + errors, + kvp => + { + Assert.Equal(string.Empty, kvp.Key); + Assert.Equal("Test error 0", kvp.Value); + }, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Error 1 Test Error 2", kvp.Value); + }, + kvp => + { + Assert.Equal("key2", kvp.Key); + Assert.Equal("Test Error 3", kvp.Value); + }, + kvp => + { + Assert.Equal("list[3].key3", kvp.Key); + Assert.Equal("Test Error 4", kvp.Value); + }); } [Fact] @@ -75,11 +99,18 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal { // Arrange var modelState = new ModelStateDictionary(); + modelState.AddModelError(string.Empty, "Test error 0"); modelState.AddModelError("key1", "Test Error 1"); modelState.AddModelError("key1", "Test Error 2"); modelState.AddModelError("key2", "Test Error 3"); + modelState.AddModelError("list[3].key3", "Test Error 4"); var serializableError = new SerializableError(modelState); var outputStream = new MemoryStream(); + var expectedContent = "" + + "Test error 0" + + "Test Error 1 Test Error 2" + + "Test Error 3" + + "Test Error 4"; // Act using (var xmlWriter = XmlWriter.Create(outputStream)) @@ -91,15 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); // Assert - var expectedContent = - TestPlatformHelper.IsMono ? - "Test Error 1 Test Error 2" + - "Test Error 3" : - "" + - "Test Error 1 Test Error 2Test Error 3"; - Assert.Equal(expectedContent, res); } } -} \ No newline at end of file +} From 0d427a60e5bd3bc18e21b6d1fee3b3fa2faab234 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 26 Jul 2018 10:12:50 -0700 Subject: [PATCH 134/316] Use `TheoryData` properties to avoid duplicate test data - couldn't just remove duplicate `[TheoryData]` in `GlobalRoutingTest` - xUnit analyzers don't understand `[InlineData]` is inherited (though runtime certainly does) --- .../GlobalRoutingTest.cs | 18 ++---- .../RoutingTestsBase.cs | 64 ++++++++++++++----- 2 files changed, 53 insertions(+), 29 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs index 574b7f8160..6266e2b9b2 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs @@ -105,10 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Global routing exposes HTTP 405s for HTTP method mismatches [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + [MemberData(nameof(AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraintsData))] public override async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( string url, string method) @@ -125,11 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Global routing exposes HTTP 405s for HTTP method mismatches [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] + [MemberData(nameof(AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheActionData))] public override async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( string method, string url) @@ -144,11 +137,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - // These verbs don't match [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] + [MemberData(nameof(AttributeRouting_MixedAcceptVerbsAndRoute_UnreachableData))] public override async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) { // Arrange @@ -161,4 +151,4 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index a3d236d318..4767f77696 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -8,7 +8,6 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Newtonsoft.Json; using Xunit; @@ -248,12 +247,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } } + public static TheoryData AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheActionData + { + get + { + return new TheoryData + { + { "Post", "/Friends" }, + { "Put", "/Friends" }, + { "Patch", "/Friends" }, + { "Options", "/Friends" }, + { "Head", "/Friends" }, + }; + } + } + [Theory] - [InlineData("Post", "/Friends")] - [InlineData("Put", "/Friends")] - [InlineData("Patch", "/Friends")] - [InlineData("Options", "/Friends")] - [InlineData("Head", "/Friends")] + [MemberData(nameof(AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheActionData))] public virtual async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( string method, string url) @@ -390,11 +400,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests result.ExpectedUrls); } + public static TheoryData AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraintsData + { + get + { + return new TheoryData + { + { "http://localhost/api/v1/Maps/5", "PATCH" }, + { "http://localhost/api/v2/Maps/5", "PATCH" }, + { "http://localhost/api/v1/Maps/PartialUpdate/5", "PUT" }, + { "http://localhost/api/v2/Maps/PartialUpdate/5", "PUT" }, + }; + } + } + [Theory] - [InlineData("http://localhost/api/v1/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v2/Maps/5", "PATCH")] - [InlineData("http://localhost/api/v1/Maps/PartialUpdate/5", "PUT")] - [InlineData("http://localhost/api/v2/Maps/PartialUpdate/5", "PUT")] + [MemberData(nameof(AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraintsData))] public virtual async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( string url, string method) @@ -1208,10 +1229,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } // These verbs don't match + public static TheoryData AttributeRouting_MixedAcceptVerbsAndRoute_UnreachableData + { + get + { + return new TheoryData + { + { "/Bank/Deposit", "GET" }, + { "/Bank/Deposit/5", "DELETE" }, + { "/Bank/Withdraw/5", "GET" }, + }; + } + } + [Theory] - [InlineData("/Bank/Deposit", "GET")] - [InlineData("/Bank/Deposit/5", "DELETE")] - [InlineData("/Bank/Withdraw/5", "GET")] + [MemberData(nameof(AttributeRouting_MixedAcceptVerbsAndRoute_UnreachableData))] public virtual async Task AttributeRouting_MixedAcceptVerbsAndRoute_Unreachable(string path, string verb) { // Arrange @@ -1277,8 +1309,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { Url = url; - Values = new Dictionary(); - Values.Add("link", string.Empty); + Values = new Dictionary + { + { "link", string.Empty } + }; } public string Url { get; set; } From 556880872d72e9a38a48283567ed021db08f2905 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 26 Jul 2018 12:22:56 -0700 Subject: [PATCH 135/316] Ensure later validations of `null` models do not overwrite `Invalid` state - #8078 --- .../Validation/ValidationVisitor.cs | 6 ++- .../ModelBinding/ParameterBinderTest.cs | 54 +++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index ecf44973cc..43c385ec7b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -80,6 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false. /// public bool ValidateComplexTypesIfChildValidationFails { get; set; } + /// /// Validates a object. /// @@ -105,7 +106,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation if (model == null && key != null && !alwaysValidateAtTopLevel) { var entry = ModelState[key]; - if (entry != null && entry.ValidationState != ModelValidationState.Valid) + + // Rationale: We might see the same model state key for two different objects and want to preserve any + // known invalidity. + if (entry != null && entry.ValidationState != ModelValidationState.Invalid) { entry.ValidationState = ModelValidationState.Valid; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index 8b7ea899ed..5b67a06b92 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -836,6 +836,60 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding }); } + // Regression test for aspnet/Mvc#8078. Later parameter should not mark entry as valid. + [Fact] + public async Task BindModelAsync_ForOverlappingParameters_InValid_WithInValidFirstParameterAndSecondNull() + { + // Arrange + var parameterDescriptor = new ParameterDescriptor + { + BindingInfo = new BindingInfo + { + BinderModelName = "id", + }, + Name = "identifier", + ParameterType = typeof(string), + }; + + var actionContext = GetControllerContext(); + var modelState = actionContext.ModelState; + + // Mimic ModelStateEntry when first parameter is [FromRoute] int id and request URI is /api/values/notAnInt + modelState.SetModelValue("id", "notAnInt", "notAnInt"); + modelState.AddModelError("id", "This is not valid."); + + var modelMetadataProvider = new TestModelMetadataProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForType(typeof(string)); + var parameterBinder = new ParameterBinder( + modelMetadataProvider, + Mock.Of(), + new DefaultObjectValidator( + modelMetadataProvider, + new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + _optionsAccessor, + NullLoggerFactory.Instance); + + // Mimic result when second parameter is [FromQuery(Name = "id")] string identifier and query is ?id + var modelBindingResult = ModelBindingResult.Success(null); + var modelBinder = CreateMockModelBinder(modelBindingResult); + + // Act + var result = await parameterBinder.BindModelAsync( + actionContext, + modelBinder, + new SimpleValueProvider(), + parameterDescriptor, + modelMetadata, + value: null); + + // Assert + Assert.True(result.IsModelSet); + Assert.False(modelState.IsValid); + var keyValuePair = Assert.Single(modelState); + Assert.Equal("id", keyValuePair.Key); + Assert.Equal(ModelValidationState.Invalid, keyValuePair.Value.ValidationState); + } + private static ControllerContext GetControllerContext() { var services = new ServiceCollection(); From c01c7075be053d8be822aeff53fcb125c7fc485c Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 28 Jul 2018 16:12:54 +1200 Subject: [PATCH 136/316] Add EndpointMetadata to ActionDescriptor and hookup CORS (#8158) --- build/dependencies.props | 144 +++---- .../Abstractions/ActionDescriptor.cs | 2 + .../ApplicationModels/SelectorModel.cs | 4 + .../ControllerActionDescriptorBuilder.cs | 4 + .../DefaultApplicationModelProvider.cs | 3 + .../Internal/MvcEndpointDataSource.cs | 14 +- .../Internal/CorsApplicationModelProvider.cs | 15 +- ...ControllerActionDescriptorProviderTests.cs | 57 +++ .../CorsApplicationModelProviderTest.cs | 32 +- .../CorsDispatchingTests.cs | 34 ++ .../CorsTests.cs | 345 +---------------- .../CorsTestsBase.cs | 359 ++++++++++++++++++ .../VersioningGlobalRoutingTests.cs | 23 ++ .../VersioningTestsBase.cs | 4 +- test/WebSites/CorsWebSite/Program.cs | 26 ++ test/WebSites/CorsWebSite/Startup.cs | 15 - .../CorsWebSite/StartupWithGlobalRouting.cs | 80 ++++ 17 files changed, 716 insertions(+), 445 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs create mode 100644 test/WebSites/CorsWebSite/Program.cs create mode 100644 test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs diff --git a/build/dependencies.props b/build/dependencies.props index e24177e98d..f20cec2237 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 2.2.0-preview1-17099 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 1.7.0 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 2.1.0 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34784 - 2.2.0-preview1-34784 + 2.2.0-preview1-34816 + 2.2.0-preview1-34816 15.6.1 4.7.49 2.0.3 diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs index a1ced7244b..20ab53e773 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs @@ -36,6 +36,8 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// public IList ActionConstraints { get; set; } + public IList EndpointMetadata { get; set; } + /// /// The set of parameters associated with this action. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs index e838e17c04..d376f6c0d2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs @@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels public SelectorModel() { ActionConstraints = new List(); + EndpointMetadata = new List(); } public SelectorModel(SelectorModel other) @@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels } ActionConstraints = new List(other.ActionConstraints); + EndpointMetadata = new List(other.EndpointMetadata); if (other.AttributeRouteModel != null) { @@ -32,5 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels public AttributeRouteModel AttributeRouteModel { get; set; } public IList ActionConstraints { get; } + + public IList EndpointMetadata { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index f343701440..f24859eb20 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing.Metadata; using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal @@ -163,6 +164,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal } AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints); + + // REVIEW: Need to get metadata from controller + actionDescriptor.EndpointMetadata = actionSelector.EndpointMetadata.ToList(); } return actionDescriptors; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs index fc380933c2..00ad6bf594 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; @@ -641,6 +642,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } AddRange(selectorModel.ActionConstraints, attributes.OfType()); + AddRange(selectorModel.EndpointMetadata, attributes); // Simple case, all HTTP method attributes apply var httpMethods = attributes @@ -652,6 +654,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (httpMethods.Length > 0) { selectorModel.ActionConstraints.Add(new HttpMethodActionConstraint(httpMethods)); + selectorModel.EndpointMetadata.Add(new HttpMethodMetadata(httpMethods)); } return selectorModel; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index dda3d9f47f..649f5febfa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -3,15 +3,18 @@ using System; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; @@ -316,6 +319,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.Add(source); metadata.Add(action); + if (action.EndpointMetadata != null) + { + metadata.AddRange(action.EndpointMetadata); + } + if (!string.IsNullOrEmpty(routeName)) { metadata.Add(new RouteNameMetadata(routeName)); @@ -334,11 +342,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // 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) + if (actionConstraint is IEndpointConstraintMetadata) { // The constraint might have been added earlier, e.g. it is also a filter descriptor if (!metadata.Contains(actionConstraint)) diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs index 4410412e28..3c5d23010d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs @@ -6,7 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.Extensions.Options; +using Microsoft.AspNetCore.Routing.Metadata; namespace Microsoft.AspNetCore.Mvc.Cors.Internal { @@ -67,17 +67,18 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal if (isCorsEnabledGlobally || corsOnController || corsOnAction) { - UpdateHttpMethodActionConstraint(actionModel); + UpdateActionToAcceptCorsPreflight(actionModel); } } } } - private static void UpdateHttpMethodActionConstraint(ActionModel actionModel) + private static void UpdateActionToAcceptCorsPreflight(ActionModel actionModel) { for (var i = 0; i < actionModel.Selectors.Count; i++) { var selectorModel = actionModel.Selectors[i]; + for (var j = 0; j < selectorModel.ActionConstraints.Count; j++) { if (selectorModel.ActionConstraints[j] is HttpMethodActionConstraint httpConstraint) @@ -85,6 +86,14 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal selectorModel.ActionConstraints[j] = new CorsHttpMethodActionConstraint(httpConstraint); } } + + for (int j = 0; j < selectorModel.EndpointMetadata.Count; j++) + { + if (selectorModel.EndpointMetadata[j] is HttpMethodMetadata httpMethodMetadata) + { + selectorModel.EndpointMetadata[j] = new HttpMethodMetadata(httpMethodMetadata.HttpMethods, true); + } + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 56b44dc4fa..db7a1e3ce1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -251,6 +252,62 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value); } + [Fact] + public void GetDescriptors_ActionWithHttpMethods_AddedToEndpointMetadata() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(AttributeRoutedController).GetTypeInfo()); + + // Assert + var action = Assert.Single(descriptors); + + Assert.NotNull(action.EndpointMetadata); + + Assert.Collection(action.EndpointMetadata, + metadata => Assert.IsType(metadata), + metadata => + { + var httpMethodMetadata = Assert.IsType(metadata); + + Assert.False(httpMethodMetadata.AcceptCorsPreflight); + Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods)); + }); + } + + [Fact] + public void GetDescriptors_ActionWithMultipleHttpMethods_SingleHttpMethodMetadata() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(NonDuplicatedAttributeRouteController).GetTypeInfo()); + + // Assert + var actions = descriptors + .OfType() + .Where(d => d.ActionName == nameof(NonDuplicatedAttributeRouteController.DifferentHttpMethods)); + + Assert.Collection(actions, + InspectElement("GET"), + InspectElement("POST"), + InspectElement("PUT"), + InspectElement("PATCH"), + InspectElement("DELETE")); + + Action InspectElement(string httpMethod) + { + return (descriptor) => + { + var httpMethodAttribute = Assert.Single(descriptor.EndpointMetadata.OfType()); + Assert.Equal(httpMethod, httpMethodAttribute.HttpMethods.Single(), ignoreCase: true); + + var httpMethodMetadata = Assert.Single(descriptor.EndpointMetadata.OfType()); + Assert.Equal(httpMethod, httpMethodMetadata.HttpMethods.Single(), ignoreCase: true); + Assert.False(httpMethodMetadata.AcceptCorsPreflight); + }; + } + } + [Fact] public void GetDescriptors_AddsControllerAndActionDefaults_ToAttributeRoutedActions() { diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs index 3b4c88964f..a9538a9b24 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Cors; @@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -36,6 +38,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] @@ -55,10 +59,12 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] - public void CreateControllerModel_CustomCorsFilter_ReplacesHttpConstraints() + public void CreateControllerModel_CustomCorsFilter_EnablesCorsPreflight() { // Arrange var corsProvider = new CorsApplicationModelProvider(); @@ -73,6 +79,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] @@ -92,6 +100,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] @@ -111,10 +121,12 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] - public void BuildActionModel_CustomCorsAuthorizationFilterOnAction_ReplacesHttpConstraints() + public void BuildActionModel_CustomCorsAuthorizationFilterOnAction_EnablesCorsPreflight() { // Arrange var corsProvider = new CorsApplicationModelProvider(); @@ -129,10 +141,12 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] - public void CreateControllerModel_EnableCorsGloballyReplacesHttpMethodConstraints() + public void CreateControllerModel_EnableCorsGloballyEnablesCorsPreflight() { // Arrange var corsProvider = new CorsApplicationModelProvider(); @@ -150,10 +164,12 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] - public void CreateControllerModel_DisableCorsGloballyReplacesHttpMethodConstraints() + public void CreateControllerModel_DisableCorsGloballyEnablesCorsPreflight() { // Arrange var corsProvider = new CorsApplicationModelProvider(); @@ -169,10 +185,12 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] - public void CreateControllerModel_CustomCorsFilterGloballyReplacesHttpMethodConstraints() + public void CreateControllerModel_CustomCorsFilterGloballyEnablesCorsPreflight() { // Arrange var corsProvider = new CorsApplicationModelProvider(); @@ -188,6 +206,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.True(httpMethodMetadata.AcceptCorsPreflight); } [Fact] @@ -206,6 +226,8 @@ namespace Microsoft.AspNetCore.Mvc.Cors.Internal var selector = Assert.Single(action.Selectors); var constraint = Assert.Single(selector.ActionConstraints, c => c is HttpMethodActionConstraint); Assert.IsNotType(constraint); + var httpMethodMetadata = Assert.Single(selector.EndpointMetadata.OfType()); + Assert.False(httpMethodMetadata.AcceptCorsPreflight); } private static ApplicationModelProviderContext GetProviderContext(Type controllerType) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs new file mode 100644 index 0000000000..f5281eee9e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs @@ -0,0 +1,34 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class CorsGlobalRoutingTests : CorsTestsBase + { + public CorsGlobalRoutingTests(MvcTestFixture fixture) + : base(fixture) + { + } + + [Fact] // This intentionally returns a 405 with global routing + public override async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/Post"); + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs index 5d92c7e6eb..4d241ddeb0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs @@ -1,354 +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.Linq; -using System.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class CorsTests : IClassFixture> + public class CorsTests : CorsTestsBase { public CorsTests(MvcTestFixture fixture) + : base(fixture) { - Client = fixture.CreateDefaultClient(); - } - - public HttpClient Client { get; } - - [Theory] - [InlineData("GET")] - [InlineData("HEAD")] - [InlineData("POST")] - public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method) - { - // Arrange - var origin = "http://example.com"; - var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Cors/GetBlogComments"); - request.Headers.Add(CorsConstants.Origin, origin); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("[\"comment1\",\"comment2\",\"comment3\"]", content); - var responseHeaders = response.Headers; - var header = Assert.Single(response.Headers); - Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key); - Assert.Equal(new[] { "*" }, header.Value.ToArray()); - } - - [Fact] - public async Task OptionsRequest_NonPreflight_ExecutesOptionsAction() - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/GetOptions"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("[\"Create\",\"Update\",\"Delete\"]", content); - Assert.Empty(response.Headers); - } - - [Fact] - public async Task PreflightRequestOnNonCorsEnabledController_ExecutesOptionsAction() - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/GetOptions"); - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("[\"Create\",\"Update\",\"Delete\"]", content); - Assert.Empty(response.Headers); - } - - [Fact] - public async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/Post"); - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); - } - - [Theory] - [InlineData("GET")] - [InlineData("HEAD")] - [InlineData("POST")] - [InlineData("PUT")] - public async Task PolicyFailed_Disallows_PreFlightRequest(string method) - { - // Arrange - var request = new HttpRequestMessage( - new HttpMethod(CorsConstants.PreflightHttpMethod), - "http://localhost/Cors/GetBlogComments"); - - // Adding a custom header makes it a non-simple request. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, method); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - // MVC applied the policy and since that did not pass, there were no access control headers. - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); - - // It should short circuit and hence no result. - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal(string.Empty, content); - } - - [Fact] - public async Task SuccessfulCorsRequest_AllowsCredentials_IfThePolicyAllowsCredentials() - { - // Arrange - var request = new HttpRequestMessage( - HttpMethod.Put, - "http://localhost/Cors/EditUserComment?userComment=abcd"); - - // Adding a custom header makes it a non-simple request. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlExposeHeaders, "exposed1,exposed2"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var responseHeaders = response.Headers; - Assert.Equal( - new[] { "http://example.com" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); - Assert.Equal( - new[] { "true" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); - Assert.Equal( - new[] { "exposed1,exposed2" }, - responseHeaders.GetValues(CorsConstants.AccessControlExposeHeaders).ToArray()); - - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("abcd", content); - } - - [Fact] - public async Task SuccessfulPreflightRequest_AllowsCredentials_IfThePolicyAllowsCredentials() - { - // Arrange - var request = new HttpRequestMessage( - new HttpMethod(CorsConstants.PreflightHttpMethod), - "http://localhost/Cors/EditUserComment?userComment=abcd"); - - // Adding a custom header makes it a non-simple request. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "PUT"); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "header1,header2"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var responseHeaders = response.Headers; - Assert.Equal( - new[] { "http://example.com" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); - Assert.Equal( - new[] { "true" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); - Assert.Equal( - new[] { "header1,header2" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); - Assert.Equal( - new[] { "PUT" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray()); - - var content = await response.Content.ReadAsStringAsync(); - Assert.Empty(content); - } - - [Fact] - public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders() - { - // Arrange - var request = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Cors/GetUserComments"); - - // Adding a custom header makes it a non simple request. - request.Headers.Add(CorsConstants.Origin, "http://example2.com"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - // MVC applied the policy and since that did not pass, there were no access control headers. - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); - - // It still have executed the action. - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("[\"usercomment1\",\"usercomment2\",\"usercomment3\"]", content); - } - - [Theory] - [InlineData("GET")] - [InlineData("HEAD")] - [InlineData("POST")] - public async Task DisableCors_ActionsCanOverride_ControllerLevel(string method) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Cors/GetExclusiveContent"); - - // Exclusive content is not available on other sites. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - - // Since there are no response headers, the client should step in to block the content. - Assert.Empty(response.Headers); - var content = await response.Content.ReadAsStringAsync(); - Assert.Equal("exclusive", content); - } - - [Theory] - [InlineData("GET")] - [InlineData("HEAD")] - [InlineData("POST")] - public async Task DisableCors_PreFlight_ActionsCanOverride_ControllerLevel(string method) - { - // Arrange - var request = new HttpRequestMessage( - new HttpMethod(CorsConstants.PreflightHttpMethod), - "http://localhost/Cors/GetExclusiveContent"); - - // Exclusive content is not available on other sites. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, method); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - // Since there are no response headers, the client should step in to block the content. - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); - - // Nothing gets executed for a pre-flight request. - var content = await response.Content.ReadAsStringAsync(); - Assert.Empty(content); - } - - [Theory] - [InlineData("http://localhost/api/store/actionusingcontrollercorssettings")] - [InlineData("http://localhost/api/store/actionwithcorssettings")] - public async Task CorsFilter_RunsBeforeOtherAuthorizationFilters(string url) - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), url); - - // Adding a custom header makes it a non-simple request. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var responseHeaders = response.Headers; - Assert.Equal( - new[] { "http://example.com" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); - Assert.Equal( - new[] { "true" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); - Assert.Equal( - new[] { "Custom" }, - responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); - - var content = await response.Content.ReadAsStringAsync(); - Assert.Empty(content); - } - - [Fact] - public async Task DisableCorsFilter_RunsBeforeOtherAuthorizationFilters() - { - // Controller has an authorization filter and Cors filter and the action has a DisableCors filter - // In this scenario, the CorsFilter should be executed before any other authorization filters - // i.e irrespective of where the Cors filter is applied(controller or action), Cors filters must - // always be executed before any other type of authorization filters. - - // Arrange - var request = new HttpRequestMessage( - new HttpMethod(CorsConstants.PreflightHttpMethod), - "http://localhost/api/store/actionwithcorsdisabled"); - - // Adding a custom header makes it a non-simple request. - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); - - // Nothing gets executed for a pre-flight request. - var content = await response.Content.ReadAsStringAsync(); - Assert.Empty(content); - } - - [Fact] - public async Task CorsFilter_OnAction_PreferredOverController_AndAuthorizationFiltersRunAfterCors() - { - // Arrange - var request = new HttpRequestMessage( - new HttpMethod(CorsConstants.PreflightHttpMethod), - "http://localhost/api/store/actionwithdifferentcorspolicy"); - request.Headers.Add(CorsConstants.Origin, "http://notexpecteddomain.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); - request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); - - // Nothing gets executed for a pre-flight request. - var content = await response.Content.ReadAsStringAsync(); - Assert.Empty(content); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs new file mode 100644 index 0000000000..899ce4e792 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs @@ -0,0 +1,359 @@ +// 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.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Microsoft.AspNetCore.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public abstract class CorsTestsBase : IClassFixture> where TStartup : class + { + protected CorsTestsBase(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("GET")] + [InlineData("HEAD")] + [InlineData("POST")] + public async Task ResourceWithSimpleRequestPolicy_Allows_SimpleRequests(string method) + { + // Arrange + var origin = "http://example.com"; + var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Cors/GetBlogComments"); + request.Headers.Add(CorsConstants.Origin, origin); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("[\"comment1\",\"comment2\",\"comment3\"]", content); + var responseHeaders = response.Headers; + var header = Assert.Single(response.Headers); + Assert.Equal(CorsConstants.AccessControlAllowOrigin, header.Key); + Assert.Equal(new[] { "*" }, header.Value.ToArray()); + } + + [Fact] + public async Task OptionsRequest_NonPreflight_ExecutesOptionsAction() + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/GetOptions"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("[\"Create\",\"Update\",\"Delete\"]", content); + Assert.Empty(response.Headers); + } + + [Fact] + public async Task PreflightRequestOnNonCorsEnabledController_ExecutesOptionsAction() + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/GetOptions"); + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("[\"Create\",\"Update\",\"Delete\"]", content); + Assert.Empty(response.Headers); + } + + [Fact] + public virtual async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/Post"); + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Theory] + [InlineData("GET")] + [InlineData("HEAD")] + [InlineData("POST")] + [InlineData("PUT")] + public async Task PolicyFailed_Disallows_PreFlightRequest(string method) + { + // Arrange + var request = new HttpRequestMessage( + new HttpMethod(CorsConstants.PreflightHttpMethod), + "http://localhost/Cors/GetBlogComments"); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, method); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + // MVC applied the policy and since that did not pass, there were no access control headers. + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + + // It should short circuit and hence no result. + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal(string.Empty, content); + } + + [Fact] + public async Task SuccessfulCorsRequest_AllowsCredentials_IfThePolicyAllowsCredentials() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Put, + "http://localhost/Cors/EditUserComment?userComment=abcd"); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlExposeHeaders, "exposed1,exposed2"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseHeaders = response.Headers; + Assert.Equal( + new[] { "http://example.com" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); + Assert.Equal( + new[] { "true" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); + Assert.Equal( + new[] { "exposed1,exposed2" }, + responseHeaders.GetValues(CorsConstants.AccessControlExposeHeaders).ToArray()); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("abcd", content); + } + + [Fact] + public async Task SuccessfulPreflightRequest_AllowsCredentials_IfThePolicyAllowsCredentials() + { + // Arrange + var request = new HttpRequestMessage( + new HttpMethod(CorsConstants.PreflightHttpMethod), + "http://localhost/Cors/EditUserComment?userComment=abcd"); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "PUT"); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "header1,header2"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseHeaders = response.Headers; + Assert.Equal( + new[] { "http://example.com" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); + Assert.Equal( + new[] { "true" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); + Assert.Equal( + new[] { "header1,header2" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); + Assert.Equal( + new[] { "PUT" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray()); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + + [Fact] + public async Task PolicyFailed_Allows_ActualRequest_WithMissingResponseHeaders() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Put, "http://localhost/Cors/GetUserComments"); + + // Adding a custom header makes it a non simple request. + request.Headers.Add(CorsConstants.Origin, "http://example2.com"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + // MVC applied the policy and since that did not pass, there were no access control headers. + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + + // It still have executed the action. + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("[\"usercomment1\",\"usercomment2\",\"usercomment3\"]", content); + } + + [Theory] + [InlineData("GET")] + [InlineData("HEAD")] + [InlineData("POST")] + public async Task DisableCors_ActionsCanOverride_ControllerLevel(string method) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(method), "http://localhost/Cors/GetExclusiveContent"); + + // Exclusive content is not available on other sites. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + // Since there are no response headers, the client should step in to block the content. + Assert.Empty(response.Headers); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("exclusive", content); + } + + [Theory] + [InlineData("GET")] + [InlineData("HEAD")] + [InlineData("POST")] + public async Task DisableCors_PreFlight_ActionsCanOverride_ControllerLevel(string method) + { + // Arrange + var request = new HttpRequestMessage( + new HttpMethod(CorsConstants.PreflightHttpMethod), + "http://localhost/Cors/GetExclusiveContent"); + + // Exclusive content is not available on other sites. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, method); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + // Since there are no response headers, the client should step in to block the content. + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + + // Nothing gets executed for a pre-flight request. + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + + [Theory] + [InlineData("http://localhost/api/store/actionusingcontrollercorssettings")] + [InlineData("http://localhost/api/store/actionwithcorssettings")] + public async Task CorsFilter_RunsBeforeOtherAuthorizationFilters(string url) + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), url); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseHeaders = response.Headers; + Assert.Equal( + new[] { "http://example.com" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); + Assert.Equal( + new[] { "true" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); + Assert.Equal( + new[] { "Custom" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + + [Fact] + public async Task DisableCorsFilter_RunsBeforeOtherAuthorizationFilters() + { + // Controller has an authorization filter and Cors filter and the action has a DisableCors filter + // In this scenario, the CorsFilter should be executed before any other authorization filters + // i.e irrespective of where the Cors filter is applied(controller or action), Cors filters must + // always be executed before any other type of authorization filters. + + // Arrange + var request = new HttpRequestMessage( + new HttpMethod(CorsConstants.PreflightHttpMethod), + "http://localhost/api/store/actionwithcorsdisabled"); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + + // Nothing gets executed for a pre-flight request. + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + + [Fact] + public async Task CorsFilter_OnAction_PreferredOverController_AndAuthorizationFiltersRunAfterCors() + { + // Arrange + var request = new HttpRequestMessage( + new HttpMethod(CorsConstants.PreflightHttpMethod), + "http://localhost/api/store/actionwithdifferentcorspolicy"); + request.Headers.Add(CorsConstants.Origin, "http://notexpecteddomain.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Empty(response.Headers); + + // Nothing gets executed for a pre-flight request. + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs index af2147949f..a79ebc9516 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -29,5 +30,27 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.True(result); } + + // This behaves differently right now because the action/endpoint constraints are always + // executed after the DFA nodes like (HttpMethodMatcherPolicy). You don't have the flexibility + // to do what this test is doing in old-style routing. + [Fact] + public override 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("AnyV2OrHigherWithId", result.Action); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs index 152c08a799..01e6ef154b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs @@ -508,7 +508,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction() + public virtual async Task VersionedApi_CanUseConstraintOrder_ToChangeSelectedAction() { // Arrange var message = new HttpRequestMessage(HttpMethod.Delete, "http://localhost/" + "Customers/5?version=2"); @@ -551,7 +551,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(path, actualUrl); } - private class RoutingResult + protected class RoutingResult { public string[] ExpectedUrls { get; set; } diff --git a/test/WebSites/CorsWebSite/Program.cs b/test/WebSites/CorsWebSite/Program.cs new file mode 100644 index 0000000000..b58c5ff2f4 --- /dev/null +++ b/test/WebSites/CorsWebSite/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 CorsWebSite +{ + 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/CorsWebSite/Startup.cs b/test/WebSites/CorsWebSite/Startup.cs index 6ee44e7f39..5c8e115755 100644 --- a/test/WebSites/CorsWebSite/Startup.cs +++ b/test/WebSites/CorsWebSite/Startup.cs @@ -76,20 +76,5 @@ namespace CorsWebSite { app.UseMvc(); } - - 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/CorsWebSite/StartupWithGlobalRouting.cs b/test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs new file mode 100644 index 0000000000..f9e2a744c7 --- /dev/null +++ b/test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs @@ -0,0 +1,80 @@ +// 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.Cors.Infrastructure; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; + +namespace CorsWebSite +{ + public class StartupWithGlobalRouting + { + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc(options => options.EnableGlobalRouting = true); + services.Configure(options => + { + options.AddPolicy( + "AllowAnySimpleRequest", + builder => + { + builder.AllowAnyOrigin() + .WithMethods("GET", "POST", "HEAD"); + }); + + options.AddPolicy( + "AllowSpecificOrigin", + builder => + { + builder.WithOrigins("http://example.com"); + }); + + options.AddPolicy( + "WithCredentials", + builder => + { + builder.AllowCredentials() + .WithOrigins("http://example.com"); + }); + + options.AddPolicy( + "WithCredentialsAnyOrigin", + builder => + { + builder.AllowCredentials() + .AllowAnyOrigin() + .AllowAnyHeader() + .WithMethods("PUT", "POST") + .WithExposedHeaders("exposed1", "exposed2"); + }); + + options.AddPolicy( + "AllowAll", + builder => + { + builder.AllowCredentials() + .AllowAnyMethod() + .AllowAnyHeader() + .AllowAnyOrigin(); + }); + + options.AddPolicy( + "Allow example.com", + builder => + { + builder.AllowCredentials() + .AllowAnyMethod() + .AllowAnyHeader() + .WithOrigins("http://example.com"); + }); + }); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvc(); + } + } +} From fbae57cde107c3a004748c60a83db3918782e7c5 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 27 Jul 2018 17:30:07 -0700 Subject: [PATCH 137/316] React to the removal of EndpointConstraint --- build/dependencies.props | 4 +- .../ConsumesAttribute.cs | 98 +--- .../MvcCoreServiceCollectionExtensions.cs | 7 +- .../Internal/ActionConstraintCache.cs | 43 +- .../Internal/IConsumesActionConstraint.cs | 9 - .../Internal/MvcEndpointDataSource.cs | 21 +- .../Routing/ActionConstraintMatcherPolicy.cs | 237 ++++++++++ .../Routing/IConsumesMetadata.cs | 12 + .../ConsumesAttributeTests.cs | 193 -------- .../MvcCoreServiceCollectionExtensionsTest.cs | 8 + .../ActionConstraintMatcherPolicyTest.cs | 425 ++++++++++++++++++ .../VersioningGlobalRoutingTests.cs | 21 +- .../VersioningTestsBase.cs | 2 +- .../RequestScopedActionConstraint.cs | 15 +- .../VersioningWebSite/VersionAttribute.cs | 8 +- .../VersionRangeValidator.cs | 8 +- .../VersionRouteAttribute.cs | 8 +- 17 files changed, 764 insertions(+), 355 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs diff --git a/build/dependencies.props b/build/dependencies.props index f20cec2237..9fc6186a0c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34816 2.2.0-preview1-34816 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 + 2.2.0-a-preview1-action-constraints-come-home-16802 + 2.2.0-a-preview1-action-constraints-come-home-16802 2.2.0-preview1-34816 2.2.0-preview1-34816 2.2.0-preview1-34816 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs index 7e047b1367..8a6efd7362 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -10,8 +10,8 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.Net.Http.Headers; using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; @@ -26,8 +26,7 @@ namespace Microsoft.AspNetCore.Mvc Attribute, IResourceFilter, IConsumesActionConstraint, - IApiRequestMetadataProvider, - IConsumesEndpointConstraint + IApiRequestMetadataProvider { public static readonly int ConsumesActionConstraintOrder = 200; @@ -58,11 +57,6 @@ namespace Microsoft.AspNetCore.Mvc /// int IActionConstraint.Order => ConsumesActionConstraintOrder; - // The value used is a non default value so that it avoids getting mixed with other endpoint constraints - // with default order. - /// - int IEndpointConstraint.Order => ConsumesActionConstraintOrder; - /// /// Gets or sets the supported request content types. Used to select an action when there would otherwise be /// multiple matches. @@ -192,83 +186,6 @@ namespace Microsoft.AspNetCore.Mvc return true; } - /// - public bool Accept(EndpointConstraintContext context) - { - // If this constraint is not closest to the endpoint, it will be skipped. - if (!IsApplicable(context.CurrentCandidate.Endpoint)) - { - // Since the constraint is to be skipped, returning true here - // will let the current candidate ignore this constraint and will - // be selected based on other constraints for this endpoint. - return true; - } - - var requestContentType = context.HttpContext.Request.ContentType; - - // If the request content type is null we need to act like pass through. - // In case there is a single candidate with a constraint it should be selected. - // If there are multiple endpoints with consumes endpoint constraints this should result in ambiguous exception - // unless there is another endpoint without a consumes constraint. - if (requestContentType == null) - { - var isEndpointWithoutConsumeConstraintPresent = context.Candidates.Any( - candidate => candidate.Constraints == null || - !candidate.Constraints.Any(constraint => constraint is IConsumesEndpointConstraint)); - - return !isEndpointWithoutConsumeConstraintPresent; - } - - // Confirm the request's content type is more specific than (a media type this endpoint supports e.g. OK - // if client sent "text/plain" data and this endpoint supports "text/*". - if (IsSubsetOfAnyContentType(requestContentType)) - { - return true; - } - - var firstCandidate = context.Candidates[0]; - if (firstCandidate.Endpoint != context.CurrentCandidate.Endpoint) - { - // If the current candidate is not same as the first candidate, - // we need not probe other candidates to see if they apply. - // Only the first candidate is allowed to probe other candidates and based on the result select itself. - return false; - } - - // Run the matching logic for all IConsumesEndpointConstraints we can find, and see what matches. - // 1). If we have a unique best match, then only that constraint should return true. - // 2). If we have multiple matches, then all constraints that match will return true - // , resulting in ambiguity(maybe). - // 3). If we have no matches, then we choose the first constraint to return true.It will later return a 415 - foreach (var candidate in context.Candidates) - { - if (candidate.Endpoint == firstCandidate.Endpoint) - { - continue; - } - - var tempContext = new EndpointConstraintContext() - { - Candidates = context.Candidates, - HttpContext = context.HttpContext, - CurrentCandidate = candidate - }; - - if (candidate.Constraints == null || candidate.Constraints.Count == 0 || - candidate.Constraints.Any(constraint => constraint is IConsumesEndpointConstraint && - constraint.Accept(tempContext))) - { - // There is someone later in the chain which can handle the request. - // end the process here. - return false; - } - } - - // There is no one later in the chain that can handle this content type return a false positive so that - // later we can detect and return a 415. - return true; - } - private bool IsApplicable(ActionDescriptor actionDescriptor) { // If there are multiple IConsumeActionConstraints which are defined at the class and @@ -280,17 +197,6 @@ namespace Microsoft.AspNetCore.Mvc filter => filter.Filter is IConsumesActionConstraint).Filter == this; } - private bool IsApplicable(Endpoint endpoint) - { - // If there are multiple IConsumeActionConstraints which are defined at the class and - // at the action level, the one closest to the action overrides the others. To ensure this - // we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an - // IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one - // closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this. - return endpoint.Metadata.Last( - metadata => metadata is IConsumesEndpointConstraint) == this; - } - private MediaTypeCollection GetContentTypes(string firstArg, string[] args) { var completeArgs = new List(); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 3d50285d76..477a67ac0a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -20,7 +20,6 @@ using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection.Extensions; -using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.Extensions.DependencyInjection @@ -173,8 +172,10 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton(); // Will be cached by the DefaultActionSelector - services.TryAddEnumerable( - ServiceDescriptor.Transient()); + services.TryAddEnumerable(ServiceDescriptor.Transient()); + + // Policies for Endpoints + services.TryAddEnumerable(ServiceDescriptor.Singleton()); // // Controller Factory diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs index 3ba049a494..ff50a463b0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal _actionConstraintProviders = actionConstraintProviders.OrderBy(item => item.Order).ToArray(); } - private InnerCache CurrentCache + internal InnerCache CurrentCache { get { @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (current == null || current.Version != actionDescriptors.Version) { - current = new InnerCache(actionDescriptors.Version); + current = new InnerCache(actionDescriptors); _currentCache = current; } @@ -165,20 +165,49 @@ namespace Microsoft.AspNetCore.Mvc.Internal return actionConstraints; } - private class InnerCache + internal class InnerCache { - public InnerCache(int version) + private readonly ActionDescriptorCollection _actions; + private bool? _hasActionConstraints; + + public InnerCache(ActionDescriptorCollection actions) { - Version = version; + _actions = actions; } public ConcurrentDictionary Entries { get; } = new ConcurrentDictionary(); - public int Version { get; } + public int Version => _actions.Version; + + public bool HasActionConstraints + { + get + { + // This is a safe race-condition, since it always transitions from null to non-null. + // All writers will always get the same result. + if (_hasActionConstraints == null) + { + var found = false; + for (var i = 0; i < _actions.Items.Count; i++) + { + var action = _actions.Items[i]; + if (action.ActionConstraints?.Count > 0) + { + found = true; + break; + } + } + + _hasActionConstraints = found; + } + + return _hasActionConstraints.Value; + } + } } - private struct CacheEntry + internal readonly struct CacheEntry { public CacheEntry(IReadOnlyList actionConstraints) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs index 2806bd496d..b004cbdcf3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace Microsoft.AspNetCore.Mvc.Internal { @@ -13,12 +12,4 @@ namespace Microsoft.AspNetCore.Mvc.Internal public interface IConsumesActionConstraint : IActionConstraint { } - - /// - /// An constraint that identifies a type which can be used to select an action - /// based on incoming request. - /// - public interface IConsumesEndpointConstraint : IEndpointConstraint - { - } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 649f5febfa..b16b9ed018 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -5,14 +5,12 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; -using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.AspNetCore.Routing.Matchers; using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; @@ -338,17 +336,22 @@ 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 + // We explicitly convert a few types of action constraints into MatcherPolicy+Metadata + // to better integrate with the DFA matcher. + // + // Other IActionConstraint data will trigger a back-compat path that can execute + // action constraints. foreach (var actionConstraint in action.ActionConstraints) { - if (actionConstraint is IEndpointConstraintMetadata) + if (actionConstraint is HttpMethodActionConstraint httpMethodActionConstraint && + !metadata.OfType().Any()) + { + metadata.Add(new HttpMethodMetadata(httpMethodActionConstraint.HttpMethods)); + } + else if (!metadata.Contains(actionConstraint)) { // The constraint might have been added earlier, e.g. it is also a filter descriptor - if (!metadata.Contains(actionConstraint)) - { - metadata.Add(actionConstraint); - } + metadata.Add(actionConstraint); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs new file mode 100644 index 0000000000..60a1abe6b1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -0,0 +1,237 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + // This is a bridge that allows us to execute IActionConstraint instance when + // used with Matcher. + internal class ActionConstraintMatcherPolicy : MatcherPolicy, IEndpointSelectorPolicy + { + private static readonly IReadOnlyList EmptyEndpoints = Array.Empty(); + + // We need to be able to run IActionConstraints on Endpoints that aren't associated + // with an action. This is a sentinel value we use when the endpoint isn't from MVC. + internal static readonly ActionDescriptor NonAction = new ActionDescriptor(); + + private readonly ActionConstraintCache _actionConstraintCache; + + public ActionConstraintMatcherPolicy(ActionConstraintCache actionConstraintCache) + { + _actionConstraintCache = actionConstraintCache; + } + + // Run really late. + public override int Order => 100000; + + public void Apply(HttpContext httpContext, CandidateSet candidateSet) + { + // PERF: we can skip over action constraints if there aren't any app-wide. + // + // Running action constraints (or just checking for them) in a candidate set + // is somewhat expensive compared to other routing operations. This should only + // happen if user-code adds action constraints. + var actions = _actionConstraintCache.CurrentCache; + if (actions.HasActionConstraints) + { + ApplyActionConstraints(httpContext, candidateSet); + } + } + + private void ApplyActionConstraints( + HttpContext httpContext, + CandidateSet candidateSet) + { + var finalMatches = EvaluateActionConstraints(httpContext, candidateSet); + + // We've computed the set of actions that still apply (and their indices) + // First, mark everything as invalid, and then mark everything in the matching + // set as valid. This is O(2n) vs O(n**2) + for (var i = 0; i < candidateSet.Count; i++) + { + candidateSet[i].IsValidCandidate = false; + } + + if (finalMatches != null) + { + for (var i = 0; i < finalMatches.Count; i++) + { + candidateSet[finalMatches[i].index].IsValidCandidate = true; + } + } + } + + // This is almost the same as the code in ActionSelector, but we can't really share the logic + // because we need to track the index of each candidate - and, each candidate has its own route + // values. + private IReadOnlyList<(int index, ActionSelectorCandidate candidate)> EvaluateActionConstraints( + HttpContext httpContext, + CandidateSet candidateSet) + { + var items = new List<(int index, ActionSelectorCandidate candidate)>(); + + // We want to execute a group at a time (based on score) so keep track of the score that we've seen. + int? score = null; + + // Perf: Avoid allocations + for (var i = 0; i < candidateSet.Count; i++) + { + ref var candidate = ref candidateSet[i]; + if (candidate.IsValidCandidate) + { + if (score != null && score != candidate.Score) + { + // This is the end of a group. + var matches = EvaluateActionConstraintsCore(httpContext, candidateSet, items, startingOrder: null); + if (matches?.Count > 0) + { + return matches; + } + + // If we didn't find matches, then reset. + items.Clear(); + } + + score = candidate.Score; + + // If we get here, this is either the first endpoint or the we just (unsuccessfully) + // executed constraints for a group. + // + // So keep adding constraints. + var endpoint = candidate.Endpoint; + var actionDescriptor = endpoint.Metadata.GetMetadata(); + + IReadOnlyList constraints = Array.Empty(); + if (actionDescriptor != null) + { + constraints = _actionConstraintCache.GetActionConstraints(httpContext, actionDescriptor); + } + + // Capture the index. We need this later to look up the endpoint/route values. + items.Add((i, new ActionSelectorCandidate(actionDescriptor ?? NonAction, constraints))); + } + } + + // Handle residue + return EvaluateActionConstraintsCore(httpContext, candidateSet, items, startingOrder: null); + } + + private IReadOnlyList<(int index, ActionSelectorCandidate candidate)> EvaluateActionConstraintsCore( + HttpContext httpContext, + CandidateSet candidateSet, + IReadOnlyList<(int index, ActionSelectorCandidate candidate)> items, + int? startingOrder) + { + // Find the next group of constraints to process. This will be the lowest value of + // order that is higher than startingOrder. + int? order = null; + + // Perf: Avoid allocations + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + var constraints = item.candidate.Constraints; + if (constraints != null) + { + for (var j = 0; j < constraints.Count; j++) + { + var constraint = constraints[j]; + if ((startingOrder == null || constraint.Order > startingOrder) && + (order == null || constraint.Order < order)) + { + order = constraint.Order; + } + } + } + } + + // If we don't find a next then there's nothing left to do. + if (order == null) + { + return items; + } + + // Since we have a constraint to process, bisect the set of endpoints into those with and without a + // constraint for the current order. + var endpointsWithConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); + var endpointsWithoutConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); + + var constraintContext = new ActionConstraintContext(); + constraintContext.Candidates = items.Select(i => i.candidate).ToArray(); + + // Perf: Avoid allocations + for (var i = 0; i < items.Count; i++) + { + var item = items[i]; + var isMatch = true; + var foundMatchingConstraint = false; + + var constraints = item.candidate.Constraints; + if (constraints != null) + { + constraintContext.CurrentCandidate = item.candidate; + for (var j = 0; j < constraints.Count; j++) + { + var constraint = constraints[j]; + if (constraint.Order == order) + { + foundMatchingConstraint = true; + + // Before we run the constraint, we need to initialize the route values. + // In global routing, the route values are per-endpoint. + constraintContext.RouteContext = new RouteContext(httpContext) + { + RouteData = new RouteData(candidateSet[item.index].Values), + }; + if (!constraint.Accept(constraintContext)) + { + isMatch = false; + break; + } + } + } + } + + if (isMatch && foundMatchingConstraint) + { + endpointsWithConstraint.Add(item); + } + else if (isMatch) + { + endpointsWithoutConstraint.Add(item); + } + } + + // If we have matches with constraints, those are better so try to keep processing those + if (endpointsWithConstraint.Count > 0) + { + var matches = EvaluateActionConstraintsCore(httpContext, candidateSet, endpointsWithConstraint, order); + if (matches?.Count > 0) + { + return matches; + } + } + + // If the set of matches with constraints can't work, then process the set without constraints. + if (endpointsWithoutConstraint.Count == 0) + { + return null; + } + else + { + return EvaluateActionConstraintsCore(httpContext, candidateSet, endpointsWithoutConstraint, order); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs new file mode 100644 index 0000000000..83e04931b7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + internal interface IConsumesMetadata + { + IReadOnlyList ContentTypes { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs index 30514d5ac8..57a0bbddf1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs @@ -12,9 +12,6 @@ using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.EndpointConstraints; -using Microsoft.AspNetCore.Routing.Matchers; -using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Net.Http.Headers; using Moq; using Xunit; @@ -296,192 +293,6 @@ namespace Microsoft.AspNetCore.Mvc Assert.True(constraint2.Accept(context)); } - private MatcherEndpoint CreateEndpoint(params IEndpointConstraint[] constraints) - { - var endpointMetadata = new EndpointMetadataCollection(constraints); - - return new MatcherEndpoint( - (r) => null, - RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), - 0, - endpointMetadata, - ""); - } - - [Theory] - [InlineData("application/json")] - [InlineData("application/json;Parameter1=12")] - [InlineData("text/xml")] - public void EndpointConstraint_Accept_MatchesForMachingRequestContentType(string contentType) - { - // Arrange - var constraint = new ConsumesAttribute("application/json", "text/xml"); - var endpoint = CreateEndpoint(constraint); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpoint, new [] { constraint }), - }; - - context.CurrentCandidate = context.Candidates[0]; - context.HttpContext = CreateHttpContext(contentType: contentType); - - // Act & Assert - Assert.True(constraint.Accept(context)); - } - - [Fact] - public void EndpointConstraint_Accept_TheFirstCandidateReturnsFalse_IfALaterOneMatches() - { - // Arrange - var constraint1 = new ConsumesAttribute("application/json", "text/xml"); - var endpoint1 = CreateEndpoint(constraint1); - - var constraint2 = new Mock(); - var endpoint2 = CreateEndpoint(constraint2.Object); - - constraint2.Setup(o => o.Accept(It.IsAny())) - .Returns(true); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpoint1, new [] { constraint1 }), - new EndpointSelectorCandidate(endpoint2, new [] { constraint2.Object }), - }; - - context.CurrentCandidate = context.Candidates[0]; - context.HttpContext = CreateHttpContext(contentType: "application/custom"); - - // Act & Assert - Assert.False(constraint1.Accept(context)); - } - - [Theory] - [InlineData("application/custom")] - [InlineData("")] - [InlineData(null)] - public void EndpointConstraint_Accept_ForNoMatchingCandidates_SelectsTheFirstCandidate(string contentType) - { - // Arrange - var constraint1 = new ConsumesAttribute("application/json", "text/xml"); - var endpoint1 = CreateEndpoint(constraint1); - - var constraint2 = new Mock(); - var endpoint2 = CreateEndpoint(constraint2.Object); - - constraint2.Setup(o => o.Accept(It.IsAny())) - .Returns(false); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpoint1, new [] { constraint1 }), - new EndpointSelectorCandidate(endpoint2, new [] { constraint2.Object }), - }; - - context.CurrentCandidate = context.Candidates[0]; - context.HttpContext = CreateHttpContext(contentType: contentType); - - // Act & Assert - Assert.True(constraint1.Accept(context)); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public void EndpointConstraint_Accept_ForNoRequestType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) - { - // Arrange - var constraint1 = new ConsumesAttribute("application/json"); - var endpointWithConstraint = CreateEndpoint(constraint1); - - var constraint2 = new ConsumesAttribute("text/xml"); - var endpointWithConstraint2 = CreateEndpoint(constraint2); - - var endpointWithoutConstraint = CreateEndpoint(); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), - new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), - new EndpointSelectorCandidate(endpointWithoutConstraint, new List()), - }; - - context.HttpContext = CreateHttpContext(contentType: contentType); - - // Act & Assert - context.CurrentCandidate = context.Candidates[0]; - Assert.False(constraint1.Accept(context)); - context.CurrentCandidate = context.Candidates[1]; - Assert.False(constraint2.Accept(context)); - } - - [Theory] - [InlineData("application/xml")] - [InlineData("application/custom")] - [InlineData("invalid/invalid")] - public void EndpointConstraint_Accept_UnrecognizedMediaType_SelectsTheCandidateWithoutConstraintIfPresent(string contentType) - { - // Arrange - var endpointWithoutConstraint = CreateEndpoint(); - var constraint1 = new ConsumesAttribute("application/json"); - var endpointWithConstraint = CreateEndpoint(constraint1); - - var constraint2 = new ConsumesAttribute("text/xml"); - var endpointWithConstraint2 = CreateEndpoint(constraint2); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), - new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), - new EndpointSelectorCandidate(endpointWithoutConstraint, new List()), - }; - - context.HttpContext = CreateHttpContext(contentType: contentType); - - // Act & Assert - context.CurrentCandidate = context.Candidates[0]; - Assert.False(constraint1.Accept(context)); - - context.CurrentCandidate = context.Candidates[1]; - Assert.False(constraint2.Accept(context)); - } - - [Theory] - [InlineData("")] - [InlineData(null)] - public void EndpointConstraint_Accept_ForNoRequestType_ReturnsTrueForAllConstraints(string contentType) - { - // Arrange - var constraint1 = new ConsumesAttribute("application/json"); - var endpointWithConstraint = CreateEndpoint(constraint1); - - var constraint2 = new ConsumesAttribute("text/xml"); - var endpointWithConstraint2 = CreateEndpoint(constraint2); - - var endpointWithoutConstraint = CreateEndpoint(); - - var context = new EndpointConstraintContext(); - context.Candidates = new List() - { - new EndpointSelectorCandidate(endpointWithConstraint, new [] { constraint1 }), - new EndpointSelectorCandidate(endpointWithConstraint2, new [] { constraint2 }), - }; - - context.HttpContext = CreateHttpContext(contentType: contentType); - - // Act & Assert - context.CurrentCandidate = context.Candidates[0]; - Assert.True(constraint1.Accept(context)); - context.CurrentCandidate = context.Candidates[1]; - Assert.True(constraint2.Accept(context)); - } - [Theory] [InlineData("application/xml")] [InlineData("application/custom")] @@ -620,9 +431,5 @@ namespace Microsoft.AspNetCore.Mvc public interface ITestActionConsumeConstraint : IConsumesActionConstraint, IResourceFilter { } - - public interface ITestEndpointConsumeConstraint : IConsumesEndpointConstraint, IResourceFilter - { - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index 3e6c558518..9660812ec5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -320,6 +321,13 @@ namespace Microsoft.AspNetCore.Mvc typeof(MiddlewareFilterBuilderStartupFilter) } }, + { + typeof(MatcherPolicy), + new Type[] + { + typeof(ActionConstraintMatcherPolicy), + } + }, }; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs new file mode 100644 index 0000000000..51a00d6ddd --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -0,0 +1,425 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Patterns; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public class ActionConstraintMatcherPolicyTest + { + [Fact] + public void Apply_CanBeAmbiguous() + { + // Arrange + var actions = new ActionDescriptor[] + { + new ActionDescriptor() { DisplayName = "A1" }, + new ActionDescriptor() { DisplayName = "A2" }, + }; + + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + + // Act + selector.Apply(new DefaultHttpContext(), candidateSet); + + // Assert + Assert.True(candidateSet[0].IsValidCandidate); + Assert.True(candidateSet[1].IsValidCandidate); + } + + [Fact] + public void Apply_PrefersActionWithConstraints() + { + // Arrange + var actionWithConstraints = new ActionDescriptor() + { + ActionConstraints = new List() + { + new HttpMethodActionConstraint(new string[] { "POST" }), + }, + Parameters = new List(), + }; + + var actionWithoutConstraints = new ActionDescriptor() + { + Parameters = new List(), + }; + + var actions = new ActionDescriptor[] { actionWithConstraints, actionWithoutConstraints }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.True(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + } + + [Fact] + public void Apply_ConstraintsRejectAll() + { + // Arrange + var action1 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = false, }, + }, + }; + + var action2 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = false, }, + }, + }; + + var actions = new ActionDescriptor[] { action1, action2 }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.False(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + } + + [Fact] + public void Apply_ConstraintsRejectAll_DifferentStages() + { + // Arrange + var action1 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = false, Order = 0 }, + new BooleanConstraint() { Pass = true, Order = 1 }, + }, + }; + + var action2 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0 }, + new BooleanConstraint() { Pass = false, Order = 1 }, + }, + }; + + var actions = new ActionDescriptor[] { action1, action2 }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.False(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + } + + // Due to ordering of stages, the first action will be better. + [Fact] + public void Apply_ConstraintsInOrder() + { + // Arrange + var best = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + }, + }; + + var worst = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 1, }, + }, + }; + + var actions = new ActionDescriptor[] { best, worst }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.True(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + } + + [Fact] + public void Apply_SkipsOverInvalidEndpoints() + { + // Arrange + var best = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + }, + }; + + var another = new ActionDescriptor(); + + var worst = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 1, }, + }, + }; + + var actions = new ActionDescriptor[] { best, another, worst }; + var candidateSet = CreateCandidateSet(actions); + candidateSet[0].IsValidCandidate = false; + candidateSet[1].IsValidCandidate = false; + + var selector = CreateSelector(actions); + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.False(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + Assert.True(candidateSet[2].IsValidCandidate); + } + + [Fact] + public void Apply_IncludesNonMvcEndpoints() + { + // Arrange + var action1 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = false, Order = 0, }, + }, + }; + + var action2 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = false, Order = 1, }, + }, + }; + + var actions = new ActionDescriptor[] { action1, null, action2 }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.False(candidateSet[0].IsValidCandidate); + Assert.True(candidateSet[1].IsValidCandidate); + Assert.False(candidateSet[2].IsValidCandidate); + } + + // Due to ordering of stages, the first action will be better. + [Fact] + public void Apply_ConstraintsInOrder_MultipleStages() + { + // Arrange + var best = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + new BooleanConstraint() { Pass = true, Order = 1, }, + new BooleanConstraint() { Pass = true, Order = 2, }, + }, + }; + + var worst = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + new BooleanConstraint() { Pass = true, Order = 1, }, + new BooleanConstraint() { Pass = true, Order = 3, }, + }, + }; + + var actions = new ActionDescriptor[] { best, worst }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.True(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + } + + [Fact] + public void Apply_Fallback_ToActionWithoutConstraints() + { + // Arrange + var nomatch1 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + new BooleanConstraint() { Pass = true, Order = 1, }, + new BooleanConstraint() { Pass = false, Order = 2, }, + }, + }; + + var nomatch2 = new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint() { Pass = true, Order = 0, }, + new BooleanConstraint() { Pass = true, Order = 1, }, + new BooleanConstraint() { Pass = false, Order = 3, }, + }, + }; + + var best = new ActionDescriptor(); + + var actions = new ActionDescriptor[] { best, nomatch1, nomatch2 }; + var candidateSet = CreateCandidateSet(actions); + + var selector = CreateSelector(actions); + + var httpContext = CreateHttpContext("POST"); + + // Act + selector.Apply(httpContext, candidateSet); + + // Assert + Assert.True(candidateSet[0].IsValidCandidate); + Assert.False(candidateSet[1].IsValidCandidate); + Assert.False(candidateSet[2].IsValidCandidate); + } + + private ActionConstraintMatcherPolicy CreateSelector(ActionDescriptor[] actions) + { + // We need to actually provide some actions with some action constraints metadata + // or else the policy will No-op. + var actionDescriptorProvider = new Mock(); + actionDescriptorProvider + .Setup(a => a.OnProvidersExecuted(It.IsAny())) + .Callback(c => + { + for (var i = 0; i < actions.Length; i++) + { + c.Results.Add(actions[i]); + } + }); + + var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + new IActionDescriptorProvider[] { actionDescriptorProvider.Object, }, + Enumerable.Empty()); + + var cache = new ActionConstraintCache(actionDescriptorCollectionProvider, new[] + { + new DefaultActionConstraintProvider(), + }); + + return new ActionConstraintMatcherPolicy(cache); + } + + private static HttpContext CreateHttpContext(string httpMethod) + { + var httpContext = new DefaultHttpContext(); + httpContext.Request.Method = httpMethod; + return httpContext; + } + + private static MatcherEndpoint CreateEndpoint(ActionDescriptor action) + { + var metadata = new List() { action, }; + return new MatcherEndpoint( + (r) => null, + RoutePatternFactory.Parse("/"), + new RouteValueDictionary(), + 0, + new EndpointMetadataCollection(metadata), + $"test: {action?.DisplayName}"); + } + + private static CandidateSet CreateCandidateSet(ActionDescriptor[] actions) + { + var candidateSet = new CandidateSet( + actions.Select(CreateEndpoint).ToArray(), + new int[actions.Length]); + + for (var i = 0; i < actions.Length; i++) + { + if (candidateSet[i].IsValidCandidate) + { + candidateSet[i].Values = new RouteValueDictionary(); + } + } + + return candidateSet; + } + + private static ActionConstraintCache GetActionConstraintCache(IActionConstraintProvider[] actionConstraintProviders = null) + { + var descriptorProvider = new ActionDescriptorCollectionProvider( + Enumerable.Empty(), + Enumerable.Empty()); + return new ActionConstraintCache(descriptorProvider, actionConstraintProviders.AsEnumerable() ?? new List()); + } + + private class BooleanConstraint : IActionConstraint + { + public bool Pass { get; set; } + + public int Order { get; set; } + + public bool Accept(ActionConstraintContext context) + { + return Pass; + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs index a79ebc9516..69af08c14b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs @@ -40,6 +40,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // 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); + } + + // This behaves differently right now because the action/endpoint constraints are always + // executed after the DFA nodes like (HttpMethodMatcherPolicy). You don't have the flexibility + // to do what this test is doing in old-style routing. + [Fact] + public override async Task VersionedApi_ConstraintOrder_IsRespected() + { + // Arrange + var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2"); + // Act var response = await Client.SendAsync(message); @@ -50,7 +69,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var result = JsonConvert.DeserializeObject(body); Assert.Equal("Customers", result.Controller); - Assert.Equal("AnyV2OrHigherWithId", result.Action); + Assert.Equal("Post", result.Action); } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs index 01e6ef154b..a2b7c36325 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs @@ -489,7 +489,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task VersionedApi_ConstraintOrder_IsRespected() + public virtual async Task VersionedApi_ConstraintOrder_IsRespected() { // Arrange var message = new HttpRequestMessage(HttpMethod.Post, "http://localhost/" + "Customers?version=2"); diff --git a/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs b/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs index 0b351b389a..ed5a267e3b 100644 --- a/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs +++ b/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs @@ -4,13 +4,12 @@ using System; using System.Collections.Concurrent; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing.EndpointConstraints; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite { // Only matches when the requestId is the same as the one passed in the constructor. - public class RequestScopedConstraintAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory + public class RequestScopedConstraintAttribute : Attribute, IActionConstraintFactory { private readonly string _requestId; private readonly Func CreateFactory = @@ -30,18 +29,13 @@ namespace BasicWebSite return CreateInstanceCore(services); } - IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) - { - return CreateInstanceCore(services); - } - private Constraint CreateInstanceCore(IServiceProvider services) { var constraintType = typeof(Constraint); return (Constraint)ActivatorUtilities.CreateInstance(services, typeof(Constraint), new[] { _requestId }); } - private class Constraint : IActionConstraint, IEndpointConstraint + private class Constraint : IActionConstraint { private readonly RequestIdService _requestIdService; private readonly string _requestId; @@ -59,11 +53,6 @@ namespace BasicWebSite return AcceptCore(); } - bool IEndpointConstraint.Accept(EndpointConstraintContext context) - { - return AcceptCore(); - } - private bool AcceptCore() { return _requestId == _requestIdService.RequestId; diff --git a/test/WebSites/VersioningWebSite/VersionAttribute.cs b/test/WebSites/VersioningWebSite/VersionAttribute.cs index 827aaef23c..5a90faf2fb 100644 --- a/test/WebSites/VersioningWebSite/VersionAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionAttribute.cs @@ -3,11 +3,10 @@ using System; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionAttribute : Attribute, IActionConstraintFactory, IEndpointConstraintFactory + public class VersionAttribute : Attribute, IActionConstraintFactory { private int? _maxVersion; private int? _minVersion; @@ -37,10 +36,5 @@ namespace VersioningWebSite { return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 }; } - - IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) - { - return new VersionRangeValidator(_minVersion, _maxVersion) { Order = _order ?? 0 }; - } } } \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/VersionRangeValidator.cs b/test/WebSites/VersioningWebSite/VersionRangeValidator.cs index ec8d244de3..858aacc16e 100644 --- a/test/WebSites/VersioningWebSite/VersionRangeValidator.cs +++ b/test/WebSites/VersioningWebSite/VersionRangeValidator.cs @@ -3,11 +3,10 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionRangeValidator : IActionConstraint, IEndpointConstraint + public class VersionRangeValidator : IActionConstraint { private readonly int? _minVersion; private readonly int? _maxVersion; @@ -30,11 +29,6 @@ namespace VersioningWebSite return ProcessRequest(context.RouteContext.HttpContext.Request); } - public bool Accept(EndpointConstraintContext context) - { - return ProcessRequest(context.HttpContext.Request); - } - private bool ProcessRequest(HttpRequest request) { int version; diff --git a/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs b/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs index 288a488790..e12ec2d805 100644 --- a/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs +++ b/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs @@ -5,11 +5,10 @@ using System; using System.Text.RegularExpressions; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Routing.EndpointConstraints; namespace VersioningWebSite { - public class VersionRouteAttribute : RouteAttribute, IActionConstraintFactory, IEndpointConstraintFactory + public class VersionRouteAttribute : RouteAttribute, IActionConstraintFactory { private readonly IActionConstraint _actionConstraint; @@ -127,10 +126,5 @@ namespace VersioningWebSite { return _actionConstraint; } - - IEndpointConstraint IEndpointConstraintFactory.CreateInstance(IServiceProvider services) - { - return (IEndpointConstraint)_actionConstraint; - } } } \ No newline at end of file From 41de26a546d565e28e757df371204348dc887dbf Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sun, 29 Jul 2018 16:58:50 +1200 Subject: [PATCH 138/316] Add UseMvc unit tests (#8164) --- .../MvcApplicationBuilderExtensionsTest.cs | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index 3b2f75f341..8e4c7820f6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -2,7 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Builder.Internal; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; @@ -49,5 +56,61 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder "in the application startup code.", exception.Message); } + + [Fact] + public void UseMvc_GlobalRoutingDisabled_NoEndpointInfos() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new DiagnosticListener("Microsoft.AspNetCore")); + services.AddLogging(); + services.AddMvcCore(o => o.EnableGlobalRouting = false); + var serviceProvider = services.BuildServiceProvider(); + var appBuilder = new ApplicationBuilder(serviceProvider); + + // Act + appBuilder.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + + var mvcEndpointDataSource = appBuilder.ApplicationServices + .GetRequiredService>() + .OfType() + .First(); + + Assert.Empty(mvcEndpointDataSource.ConventionalEndpointInfos); + } + + [Fact] + public void UseMvc_GlobalRoutingEnabled_NoEndpointInfos() + { + // Arrange + var services = new ServiceCollection(); + services.AddSingleton(new DiagnosticListener("Microsoft.AspNetCore")); + services.AddLogging(); + services.AddMvcCore(o => o.EnableGlobalRouting = true); + var serviceProvider = services.BuildServiceProvider(); + var appBuilder = new ApplicationBuilder(serviceProvider); + + // Act + appBuilder.UseMvc(routes => + { + routes.MapRoute( + name: "default", + template: "{controller=Home}/{action=Index}/{id?}"); + }); + + var mvcEndpointDataSource = appBuilder.ApplicationServices + .GetRequiredService>() + .OfType() + .First(); + + var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos); + Assert.Equal("default", endpointInfo.Name); + Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Template); + } } } From 29f3e94fd1d4f1dbd34e26c8dcab4982c6831b22 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 29 Jul 2018 12:20:41 -0700 Subject: [PATCH 139/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 151 ++++++++++++++++++++------------------- korebuild-lock.txt | 4 +- 2 files changed, 78 insertions(+), 77 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 9fc6186a0c..78e6a161ca 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.42.1 2.1.0 2.1.0-rc1-final - 2.2.0-preview1-34816 - 2.2.0-preview1-17099 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-a-preview1-action-constraints-come-home-16802 - 2.2.0-a-preview1-action-constraints-come-home-16802 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 + 2.2.0-preview1-34823 + 2.2.0-preview1-17102 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34816 + 2.2.0-preview1-34823 1.7.0 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 2.1.0 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34816 - 2.2.0-preview1-34816 + 2.2.0-preview1-34823 + 2.2.0-preview1-34823 15.6.1 4.7.49 2.0.3 @@ -104,9 +104,10 @@ 4.5.0 4.5.0 4.5.1 - 0.9.0 + 0.10.0 2.3.1 - 2.4.0-rc.1.build4038 + 2.4.0 + diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 27e2e80f9a..6b8da29e6b 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17099 -commithash:263ed1db9866b6b419b1f5d5189a712aa218acb3 +version:2.2.0-preview1-17102 +commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 From 0102d4efab9480d443bea0dc896390a7516c5a88 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 26 Jul 2018 12:09:59 -0700 Subject: [PATCH 140/316] Introduce ApiConventionMethodAttribute Fixes #8147 --- .../ApiControllerSymbolCache.cs | 3 + .../SymbolApiResponseMetadataProvider.cs | 60 ++++++++- .../SymbolNames.cs | 2 + .../ApiConventionMethodAttribute.cs | 76 +++++++++++ .../ApiConventionTypeAttribute.cs | 2 +- .../ApiExplorer/ApiConventionResult.cs | 35 +++-- .../Properties/Resources.Designer.cs | 28 ++++ .../Resources.resx | 6 + .../ApiResponseTypeProviderTest.cs | 48 +++++++ .../ApiConventionMethodAttributeTest.cs | 122 ++++++++++++++++++ .../ApiExplorer/ApiConventionResultTest.cs | 26 ++++ .../ApiExplorerTest.cs | 28 ++++ .../ApiConventionAnalyzerIntegrationTest.cs | 4 + .../SymbolApiResponseMetadataProviderTest.cs | 49 +++++++ ...ionMethod_ReturnsUndocumentedStatusCode.cs | 16 +++ .../GetResponseMetadataTests.cs | 7 + ...ResponseTypeWithApiConventionController.cs | 11 ++ 17 files changed, 503 insertions(+), 20 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs index 5866ec36ba..74e687e710 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs @@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public ApiControllerSymbolCache(Compilation compilation) { ActionResultOfT = compilation.GetTypeByMetadataName(SymbolNames.ActionResultOfT); + ApiConventionMethodAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionMethodAttribute); ApiConventionNameMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionNameMatchAttribute); ApiConventionTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeAttribute); ApiConventionTypeMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeMatchAttribute); @@ -30,6 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public INamedTypeSymbol ActionResultOfT { get; } + public INamedTypeSymbol ApiConventionMethodAttribute { get; } + public INamedTypeSymbol ApiConventionNameMatchAttribute { get; } public INamedTypeSymbol ApiConventionTypeAttribute { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs index 9fbe1916ff..9f51139f23 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -36,6 +36,58 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers ApiControllerSymbolCache symbolCache, IMethodSymbol method, IReadOnlyList attributes) + { + var conventionMethod = GetMethodFromConventionMethodAttribute(symbolCache, method); + if (conventionMethod == null) + { + conventionMethod = MatchConventionMethod(symbolCache, method, attributes); + } + + if (conventionMethod != null) + { + return GetResponseMetadataFromMethodAttributes(symbolCache, conventionMethod); + } + + return Array.Empty(); + } + + private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + { + var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true) + .FirstOrDefault(); + + if (attribute == null) + { + return null; + } + + if (attribute.ConstructorArguments.Length != 2) + { + return null; + } + + if (attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type || + !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType)) + { + return null; + } + + if (attribute.ConstructorArguments[1].Kind != TypedConstantKind.Primitive || + !(attribute.ConstructorArguments[1].Value is string conventionMethodName)) + { + return null; + } + + var conventionMethod = conventionType.GetMembers(conventionMethodName) + .FirstOrDefault(m => m.Kind == SymbolKind.Method && m.IsStatic && m.DeclaredAccessibility == Accessibility.Public); + + return (IMethodSymbol)conventionMethod; + } + + private static IMethodSymbol MatchConventionMethod( + ApiControllerSymbolCache symbolCache, + IMethodSymbol method, + IReadOnlyList attributes) { foreach (var attribute in attributes) { @@ -53,16 +105,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers continue; } - if (!SymbolApiConventionMatcher.IsMatch(symbolCache, method, conventionMethod)) + if (SymbolApiConventionMatcher.IsMatch(symbolCache, method, conventionMethod)) { - continue; + return conventionMethod; } - - return GetResponseMetadataFromMethodAttributes(symbolCache, conventionMethod); } } - return Array.Empty(); + return null; } private static IList GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index d57a911f2b..32b7cf62f3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -7,6 +7,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; + public const string ApiConventionMethodAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute"; + public const string ApiConventionNameMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchAttribute"; public const string ApiConventionTypeMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionTypeMatchAttribute"; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs new file mode 100644 index 0000000000..a54f9a0c07 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs @@ -0,0 +1,76 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Core; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// API conventions to be applied to a controller action. + /// + /// API conventions are used to influence the output of ApiExplorer. + /// can be used to specify an exact convention method that applies + /// to an action. for details about applying conventions at + /// the assembly or controller level. + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ApiConventionMethodAttribute : Attribute + { + /// + /// Initializes an instance using and + /// the specified . + /// + /// + /// The of the convention. + /// + /// Conventions must be static types. Methods in a convention are + /// matched to an action method using rules specified by + /// that may be applied to a method name or it's parameters and + /// that are applied to parameters. + /// + /// + /// The method name. + public ApiConventionMethodAttribute(Type conventionType, string methodName) + { + ConventionType = conventionType ?? throw new ArgumentNullException(nameof(conventionType)); + ApiConventionTypeAttribute.EnsureValid(conventionType); + + if (string.IsNullOrEmpty(methodName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(methodName)); + } + + Method = GetConventionMethod(conventionType, methodName); + } + + private static MethodInfo GetConventionMethod(Type conventionType, string methodName) + { + var methods = conventionType.GetMethods(BindingFlags.Public | BindingFlags.Static) + .Where(method => method.Name == methodName) + .ToArray(); + + if (methods.Length == 0) + { + throw new ArgumentException(Resources.FormatApiConventionMethod_NoMethodFound(methodName, conventionType), nameof(methodName)); + } + else if (methods.Length > 1) + { + throw new ArgumentException(Resources.FormatApiConventionMethod_AmbigiousMethodName(methodName, conventionType), nameof(methodName)); + } + + return methods[0]; + } + + /// + /// Gets the convention type. + /// + public Type ConventionType { get; } + + internal MethodInfo Method { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs index e8c10cd3ac..e546a258ea 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc /// public Type ConventionType { get; } - private static void EnsureValid(Type conventionType) + internal static void EnsureValid(Type conventionType) { if (!conventionType.IsSealed || !conventionType.IsAbstract) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs index 28890d7e13..aaef7d2d24 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs @@ -26,31 +26,38 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer ApiConventionTypeAttribute[] apiConventionAttributes, out ApiConventionResult result) { - foreach (var attribute in apiConventionAttributes) + var apiConventionMethodAttribute = method.GetCustomAttribute(inherit: true); + var conventionMethod = apiConventionMethodAttribute?.Method; + if (conventionMethod == null) { - var conventionMethod = GetConventionMethod(method, attribute.ConventionType); - if (conventionMethod != null) - { - var metadataProviders = conventionMethod.GetCustomAttributes(inherit: false) - .OfType() - .ToArray(); + conventionMethod = GetConventionMethod(method, apiConventionAttributes); + } - result = new ApiConventionResult(metadataProviders); - return true; - } + if (conventionMethod != null) + { + var metadataProviders = conventionMethod.GetCustomAttributes(inherit: false) + .OfType() + .ToArray(); + + result = new ApiConventionResult(metadataProviders); + return true; } result = null; return false; } - private static MethodInfo GetConventionMethod(MethodInfo method, Type conventionType) + private static MethodInfo GetConventionMethod(MethodInfo method, ApiConventionTypeAttribute[] apiConventionAttributes) { - foreach (var conventionMethod in conventionType.GetMethods(BindingFlags.Public | BindingFlags.Static)) + foreach (var attribute in apiConventionAttributes) { - if (ApiConventionMatcher.IsMatch(method, conventionMethod)) + var conventionMethods = attribute.ConventionType.GetMethods(BindingFlags.Public | BindingFlags.Static); + foreach (var conventionMethod in conventionMethods) { - return conventionMethod; + if (ApiConventionMatcher.IsMatch(method, conventionMethod)) + { + return conventionMethod; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 7612620733..65b63b8d27 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1508,6 +1508,34 @@ namespace Microsoft.AspNetCore.Mvc.Core internal static string FormatApiConvention_UnsupportedAttributesOnConvention(object p0, object p1, object p2) => string.Format(CultureInfo.CurrentCulture, GetString("ApiConvention_UnsupportedAttributesOnConvention"), p0, p1, p2); + /// + /// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + /// + internal static string ApiConventionMethod_AmbigiousMethodName + { + get => GetString("ApiConventionMethod_AmbigiousMethodName"); + } + + /// + /// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + /// + internal static string FormatApiConventionMethod_AmbigiousMethodName(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_AmbigiousMethodName"), p0, p1); + + /// + /// A method named '{0}' was not found on convention type '{1}'. + /// + internal static string ApiConventionMethod_NoMethodFound + { + get => GetString("ApiConventionMethod_NoMethodFound"); + } + + /// + /// A method named '{0}' was not found on convention type '{1}'. + /// + internal static string FormatApiConventionMethod_NoMethodFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_NoMethodFound"), p0, p1); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 652f0df157..4b31a83761 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -451,4 +451,10 @@ Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}. + + Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + + + A method named '{0}' was not found on convention type '{1}'. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 29543d26f1..11c9bcb8e0 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -261,6 +261,54 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } + public class GetApiResponseTypes_WithApiConventionMethodAndProducesResponseType : ControllerBase + { + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Get))] + [ProducesResponseType(201)] + [ProducesResponseType(404)] + public Task> Put(int id, BaseModel model) => null; + } + + [Fact] + public void GetApiResponseTypes_ReturnsValuesFromProducesResponseType_IfApiConventionMethodAndAttributesAreSpecified() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_WithApiConventionMethodAndProducesResponseType), + nameof(GetApiResponseTypes_WithApiConventionMethodAndProducesResponseType.Put)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(404), + new ProducesDefaultResponseTypeAttribute(), + }); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs new file mode 100644 index 0000000000..01fc4f3773 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs @@ -0,0 +1,122 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class ApiConventionMethodAttributeTest + { + [Fact] + public void Constructor_ThrowsIfConventionMethodIsAnnotatedWithProducesAttribute() + { + // Arrange + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + + var expected = GetErrorMessage(methodName, attribute); + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionMethodAttribute(typeof(ConventionWithProducesAttribute), nameof(ConventionWithProducesAttribute.Get)), + "conventionType", + expected); + } + + public static class ConventionWithProducesAttribute + { + [Produces(typeof(void))] + public static void Get() { } + } + + [Fact] + public void Constructor_ThrowsIfTypeIsNotStatic() + { + // Arrange + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + + var expected = $"API convention type '{typeof(object)}' must be a static type."; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionMethodAttribute(typeof(object), nameof(object.ToString)), + "conventionType", + expected); + } + + [Fact] + public void Constructor_ThrowsIfMethodCannotBeFound() + { + // Arrange + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + var type = typeof(TestConventions); + + var expected = $"A method named 'DoesNotExist' was not found on convention type '{type}'."; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionMethodAttribute(typeof(TestConventions), "DoesNotExist"), + "methodName", + expected); + } + + [Fact] + public void Constructor_ThrowsIfMethodIsNotPublic() + { + // Arrange + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + var type = typeof(TestConventions); + + var expected = $"A method named 'NotPublic' was not found on convention type '{type}'."; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionMethodAttribute(typeof(TestConventions), "NotPublic"), + "methodName", + expected); + } + + [Fact] + public void Constructor_ThrowsIfMethodIsAmbigous() + { + // Arrange + var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); + var attribute = typeof(ProducesAttribute); + var type = typeof(TestConventions); + + var expected = $"Method name 'Method' is ambigous for convention type '{type}'. More than one method found with the name 'Method'."; + + // Act & Assert + ExceptionAssert.ThrowsArgument( + () => new ApiConventionMethodAttribute(typeof(TestConventions), nameof(TestConventions.Method)), + "methodName", + expected); + } + + private static class TestConventions + { + internal static void NotPublic() { } + + public static void Method(int value) { } + + public static void Method(string value) { } + } + + private static string GetErrorMessage(string methodName, params Type[] attributes) + { + return $"Method {methodName} is decorated with the following attributes that are not allowed on an API convention method:" + + Environment.NewLine + + string.Join(Environment.NewLine, attributes.Select(a => a.FullName)) + + Environment.NewLine + + $"The following attributes are allowed on API convention methods: {nameof(ProducesResponseTypeAttribute)}, {nameof(ProducesDefaultResponseTypeAttribute)}, {nameof(ApiConventionNameMatchAttribute)}"; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs index 851e607880..a1b1bd5bcc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs @@ -182,6 +182,29 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer r => Assert.Equal(404, r.StatusCode)); } + [Fact] + public void GetApiConvention_UsesApiConventionMethod() + { + // Arrange + var method = typeof(DefaultConventionController) + .GetMethod(nameof(DefaultConventionController.EditUser)); + var conventions = new[] + { + new ApiConventionTypeAttribute(typeof(DefaultApiConventions)), + }; + + // Act + var result = ApiConventionResult.TryGetApiConvention(method, conventions, out var conventionResult); + + // Assert + Assert.True(result); + Assert.Collection( + conventionResult.ResponseMetadataProviders.OrderBy(o => o.StatusCode), + r => Assert.IsAssignableFrom(r), + r => Assert.Equal(201, r.StatusCode), + r => Assert.Equal(400, r.StatusCode)); + } + public class DefaultConventionController { public IActionResult GetUser(Guid id) => null; @@ -191,6 +214,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer public IActionResult PutUser(Guid userId, User user) => null; public IActionResult Delete(Guid userId) => null; + + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Post))] + public IActionResult EditUser(int id, User user) => null; } public class User { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 9b556a42a1..d216ad4aff 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1349,6 +1349,34 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }); } + [Fact] + public async Task ApiConvention_ForActionWtihApiConventionMethod() + { + // Act + var response = await Client.PostAsync( + "ApiExplorerResponseTypeWithApiConventionController/PostItem", + new StringContent(string.Empty)); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(302, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(409, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }); + } + private IEnumerable GetSortedMediaTypes(ApiExplorerResponseType apiResponseType) { return apiResponseType.ResponseFormats diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index 0fdd22a66a..41ada495e3 100644 --- a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -63,6 +63,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public Task DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode() => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 400); + [Fact] + public Task DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode() + => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 202); + [Fact] public Task DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation() => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); diff --git a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index 32f764fb67..ace7293af7 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -182,6 +182,55 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers }); } + [Fact] + public async Task GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute)).First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(200, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + }, + metadata => + { + Assert.Equal(404, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + }); + } + + [Fact] + public async Task GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod() + { + // Arrange + var compilation = await GetResponseMetadataCompilation(); + var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod)).First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + + // Assert + Assert.Collection( + result, + metadata => + { + Assert.Equal(204, metadata.StatusCode); + Assert.NotNull(metadata.Attribute); + }); + } + [Fact] public async Task GetResponseMetadata_IgnoresCustomResponseTypeMetadataProvider() { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs new file mode 100644 index 0000000000..1b7dcc3454 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ApiController] + public class DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode : ControllerBase + { + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Post))] + public IActionResult Get(int id) + { + /*MM*/return Accepted(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs index 34e3b87237..a296f9b93f 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs +++ b/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs @@ -42,6 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [CustomInvalidProducesResponseType(Type = typeof(Person), StatusCode = "204")] public IActionResult ActionWithProducesResponseTypeWithIncorrectStatusCodeType() => null; + + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Find))] + public IActionResult GetResponseMetadata_ReturnsValuesFromApiConventionMethodAttribute() => null; + + [ProducesResponseType(204)] + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Find))] + public IActionResult GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod() => null; } public class Person { } diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs index 4a63fc746a..8d831395be 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs @@ -35,5 +35,16 @@ namespace ApiExplorerWebSite [HttpDelete] public Task DeleteProductAsync(object id) => null; + + [HttpPost] + [ApiConventionMethod(typeof(CustomConventions), nameof(CustomConventions.CustomConventionMethod))] + public Task PostItem(Product p) => null; + } + + public static class CustomConventions + { + [ProducesResponseType(302)] + [ProducesResponseType(409)] + public static void CustomConventionMethod() { } } } \ No newline at end of file From 367717760b3bcc651de651e344e70412025f7239 Mon Sep 17 00:00:00 2001 From: Justin Kotalik Date: Mon, 30 Jul 2018 12:05:41 -0700 Subject: [PATCH 141/316] Handle subtype with suffix being a subtype without a suffix (#8170) --- .../Formatters/MediaType.cs | 13 ++++++++++--- .../Formatters/InputFormatterTest.cs | 6 +++--- .../Formatters/MediaTypeTest.cs | 3 +++ .../JsonInputFormatterTest.cs | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs index ea332084ca..00e2459f09 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -488,9 +488,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } else { - // The set has no suffix, so we're just looking for an exact match (which means that if 'this' - // has a suffix, it won't match). - return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase); + // If this subtype or suffix matches the subtype of the set, + // it is considered a subtype. + // Ex: application/json > application/val+json + return MatchesEitherSubtypeOrSuffix(set); } } @@ -507,6 +508,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters return set.SubTypeSuffix.Equals(SubTypeSuffix, StringComparison.OrdinalIgnoreCase); } + private bool MatchesEitherSubtypeOrSuffix(MediaType set) + { + return set.SubType.Equals(SubType, StringComparison.OrdinalIgnoreCase) || + set.SubType.Equals(SubTypeSuffix, StringComparison.OrdinalIgnoreCase); + } + private bool ContainsAllParameters(MediaTypeParameterParser setParameters) { var parameterFound = true; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs index 2ad5752053..d5b631d9b6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs @@ -265,6 +265,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters [Theory] [InlineData("application/xml")] + [InlineData("application/mathml-content+xml")] + [InlineData("application/mathml-presentation+xml")] + [InlineData("application/mathml+xml; test=value")] public void XMLFormatter_CanRead_ReturnsTrueForSupportedMediaTypes(string requestContentType) { // Arrange @@ -289,9 +292,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } [Theory] - [InlineData("application/mathml-content+xml")] - [InlineData("application/mathml-presentation+xml")] - [InlineData("application/mathml+xml; undefined=ignored")] [InlineData("application/octet-stream; padding=3")] [InlineData("application/xml-dtd; undefined=ignored")] [InlineData("multipart/mixed; boundary=gc0p4Jq0M2Yt08j34c0p")] diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs index 28591cf017..9ab131f8ec 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs @@ -214,6 +214,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters [InlineData("application/entity+json", "application/entity+json")] [InlineData("application/*+json", "application/entity+json")] [InlineData("application/*", "application/entity+json")] + [InlineData("application/json", "application/vnd.restful+json")] + [InlineData("application/json", "application/problem+json")] public void IsSubsetOf_ReturnsTrueWhenExpected(string set, string subset) { // Arrange @@ -242,6 +244,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters [InlineData("application/*+*", "application/json")] [InlineData("application/entity+*", "application/entity+json")] // We don't allow suffixes to be wildcards [InlineData("application/*+*", "application/entity+json")] // We don't allow suffixes to be wildcards + [InlineData("application/entity+json", "application/entity")] public void IsSubsetOf_ReturnsFalseWhenExpected(string set, string subset) { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs index d17e4748fa..30868308ad 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs @@ -254,7 +254,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters [InlineData("application/some.entity+json;v=2", true)] [InlineData("application/some.entity+xml", false)] [InlineData("application/some.entity+*", false)] - [InlineData("text/some.entity+json", false)] + [InlineData("text/some.entity+json", true)] [InlineData("", false)] [InlineData(null, false)] [InlineData("invalid", false)] From d346255db64e7e7703d3ee38d4bb58d42ac532e7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 27 Jul 2018 15:08:54 -0700 Subject: [PATCH 142/316] Add analyzer and codefix that suggests removing unnecessary invalid model state validity checks Fixes #8146 --- ...ireExplicitModelValidationCheckAnalyzer.cs | 94 ----- ...icitModelValidationCheckCodeFixProvider.cs | 46 --- ...ireExplicitModelValidationCheckAnalyzer.cs | 218 +++++++++++ ...eExplicitModelValidationCodeFixProvider.cs | 69 ++++ .../ApiControllerFacts.cs | 37 ++ .../ApiControllerSymbolCache.cs | 3 + .../ApiConventionAnalyzer.cs | 32 +- .../DiagnosticDescriptors.cs | 11 + .../SymbolNames.cs | 6 +- ...equireExplicitModelValidationCheckFacts.cs | 359 ------------------ ...lValidationCheckAnalyzerIntegrationTest.cs | 86 +++++ ...ModelValidationCheckCodeFixProviderTest.cs | 63 +++ ...lyzerTest.cs => ApiControllerFactsTest.cs} | 24 +- .../MvcDiagnosticAnalyzerRunner.cs | 5 + ...urned_ForApiActionsWithModelStateChecks.cs | 22 ++ ...ctionsWithModelStateChecksUsingEquality.cs | 22 ++ ...tionsWithModelStateChecksWithoutBracing.cs | 18 + ...rApiActionsCheckingAdditionalConditions.cs | 17 + ...urning400FromNonModelStateIsValidBlocks.cs | 17 + ...ningNot400FromNonModelStateIsValidBlock.cs | 17 + ...ed_ForApiActionsWithoutModelStateChecks.cs | 16 + ...gnosticsAreReturned_ForNonApiController.cs | 15 + ...agnosticsAreReturned_ForRazorPageModels.cs | 17 + ...odeFixRemovesIfBlockWithoutBraces.Input.cs | 18 + ...deFixRemovesIfBlockWithoutBraces.Output.cs | 14 + ...teIsInvalidBlockWithEqualityCheck.Input.cs | 22 ++ ...eIsInvalidBlockWithEqualityCheck.Output.cs | 17 + ...StateIsInvalidBlockWithIfNotCheck.Input.cs | 22 ++ ...tateIsInvalidBlockWithIfNotCheck.Output.cs | 17 + .../TestFile.cs} | 0 30 files changed, 780 insertions(+), 544 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs create mode 100644 test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs create mode 100644 test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs rename test/Mvc.Analyzers.Test/{ApiConventionAnalyzerTest.cs => ApiControllerFactsTest.cs} (78%) create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs rename test/Mvc.Analyzers.Test/TestFiles/{ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs => ApiControllerFactsTest/TestFile.cs} (100%) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs deleted file mode 100644 index 1e66b2f087..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer : ApiControllerAnalyzerBase - { - public ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter) - { - } - - protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSyntaxNodeAction(context => - { - var methodSyntax = (MethodDeclarationSyntax)context.Node; - if (methodSyntax.Body == null) - { - // Ignore expression bodied methods. - return; - } - - var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - if (!analyzerContext.IsApiAction(method)) - { - return; - } - - if (method.ReturnsVoid || method.ReturnType == analyzerContext.SystemThreadingTaskOfT) - { - // Void or Task returning methods. We don't have to check anything here since we're specifically - // looking for return BadRequest(..); - return; - } - - // Only look for top level statements that look like "if (!ModelState.IsValid)" - foreach (var memberAccessSyntax in methodSyntax.Body.DescendantNodes().OfType()) - { - var ancestorIfStatement = memberAccessSyntax.FirstAncestorOrSelf(); - if (ancestorIfStatement == null) - { - // Node's not in an if statement. - continue; - } - - var symbolInfo = context.SemanticModel.GetSymbolInfo(memberAccessSyntax, context.CancellationToken); - - if (!(symbolInfo.Symbol is IPropertySymbol property) || - (property.ContainingType != analyzerContext.ModelStateDictionary) || - !string.Equals(property.Name, "IsValid", StringComparison.Ordinal) || - !IsFalseExpression(memberAccessSyntax)) - { - continue; - } - - var containingBlock = (SyntaxNode)ancestorIfStatement; - if (containingBlock.Parent.Kind() == SyntaxKind.ElseClause) - { - containingBlock = containingBlock.Parent; - } - context.ReportDiagnostic(Diagnostic.Create(SupportedDiagnostic, containingBlock.GetLocation())); - return; - } - }, SyntaxKind.MethodDeclaration); - } - - private static bool IsFalseExpression(MemberAccessExpressionSyntax memberAccessSyntax) - { - switch (memberAccessSyntax.Parent.Kind()) - { - case SyntaxKind.LogicalNotExpression: - // !ModelState.IsValid - return true; - case SyntaxKind.EqualsExpression: - var binaryExpression = (BinaryExpressionSyntax)memberAccessSyntax.Parent; - // ModelState.IsValid == false - // false == ModelState.IsValid - return binaryExpression.Left.Kind() == SyntaxKind.FalseLiteralExpression || - binaryExpression.Right.Kind() == SyntaxKind.FalseLiteralExpression; - } - - return false; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs deleted file mode 100644 index b238ad8ece..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - const string title = "Remove ModelState.IsValid check"; - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: CreateChangedDocumentAsync, - equivalenceKey: title), - context.Diagnostics); - - async Task CreateChangedDocumentAsync(CancellationToken cancellationToken) - { - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - var node = rootNode.FindNode(context.Span); - editor.RemoveNode(node); - - return editor.GetChangedDocument(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs new file mode 100644 index 0000000000..bdd8dbac75 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -0,0 +1,218 @@ +// 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.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( + DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(compilationStartAnalysisContext => + { + var symbolCache = new ApiControllerSymbolCache(compilationStartAnalysisContext.Compilation); + if (symbolCache.ApiConventionTypeAttribute == null || symbolCache.ApiConventionTypeAttribute.TypeKind == TypeKind.Error) + { + // No-op if we can't find types we care about. + return; + } + + InitializeWorker(compilationStartAnalysisContext, symbolCache); + }); + } + + private void InitializeWorker(CompilationStartAnalysisContext context, ApiControllerSymbolCache symbolCache) + { + context.RegisterOperationAction(operationAnalysisContext => + { + var ifOperation = (IConditionalOperation)operationAnalysisContext.Operation; + if (!(ifOperation.Syntax is IfStatementSyntax ifStatement)) + { + return; + } + + if (ifOperation.WhenTrue == null || ifOperation.WhenFalse != null) + { + // We only support expressions of the format + // if (!ModelState.IsValid) + // or + // if (ModelState.IsValid == false) + // If the conditional is misisng a true condition or has an else expression, skip this operation. + return; + } + + var parent = ifOperation.Parent; + if (parent?.Kind == OperationKind.Block) + { + parent = parent?.Parent; + } + + if (parent?.Kind != OperationKind.MethodBodyOperation) + { + // Only support top-level ModelState IsValid checks. + return; + } + + var trueStatement = UnwrapSingleStatementBlock(ifOperation.WhenTrue); + if (trueStatement.Kind != OperationKind.Return) + { + // We need to verify that the if statement does a ModelState.IsValid check and that the block inside contains + // a single return statement returning a 400. We'l get to it in just a bit + return; + } + + var methodSyntax = (MethodDeclarationSyntax)parent.Syntax; + var semanticModel = operationAnalysisContext.Compilation.GetSemanticModel(methodSyntax.SyntaxTree); + var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax, operationAnalysisContext.CancellationToken); + + if (!ApiControllerFacts.IsApiControllerAction(symbolCache, methodSymbol)) + { + // Not a ApiController. Nothing to do here. + return; + } + + if (!IsModelStateIsValidCheck(symbolCache, ifOperation.Condition)) + { + return; + } + + var returnOperation = (IReturnOperation)trueStatement; + + var returnValue = returnOperation.ReturnedValue; + if (returnValue == null || + !symbolCache.IActionResult.IsAssignableFrom(returnValue.Type)) + { + return; + } + + var returnStatementSyntax = (ReturnStatementSyntax)returnOperation.Syntax; + var actualMetadata = SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( + symbolCache, + semanticModel, + returnStatementSyntax, + operationAnalysisContext.CancellationToken); + + if (actualMetadata == null || actualMetadata.Value.StatusCode != 400) + { + return; + } + + var additionalLocations = new[] + { + ifStatement.GetLocation(), + returnStatementSyntax.GetLocation(), + }; + + operationAnalysisContext.ReportDiagnostic( + Diagnostic.Create( + DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck, + ifStatement.GetLocation(), + additionalLocations: additionalLocations)); + }, OperationKind.Conditional); + } + + private bool IsModelStateIsValidCheck(in ApiControllerSymbolCache symbolCache, IOperation condition) + { + switch (condition.Kind) + { + case OperationKind.UnaryOperator: + var operation = ((IUnaryOperation)condition).Operand; + return IsModelStateIsValidPropertyAccessor(symbolCache, operation); + + case OperationKind.BinaryOperator: + var binaryOperation = (IBinaryOperation)condition; + if (binaryOperation.OperatorKind == BinaryOperatorKind.Equals) + { + // (ModelState.IsValid == false) OR (false == ModelState.IsValid) + return EvaluateBinaryOperator(symbolCache, binaryOperation.LeftOperand, binaryOperation.RightOperand, false) || + EvaluateBinaryOperator(symbolCache, binaryOperation.RightOperand, binaryOperation.LeftOperand, false); + } + else if (binaryOperation.OperatorKind == BinaryOperatorKind.NotEquals) + { + // (ModelState.IsValid != true) OR (true != ModelState.IsValid) + return EvaluateBinaryOperator(symbolCache, binaryOperation.LeftOperand, binaryOperation.RightOperand, true) || + EvaluateBinaryOperator(symbolCache, binaryOperation.RightOperand, binaryOperation.LeftOperand, true); + } + return false; + + default: + return false; + } + } + + private bool EvaluateBinaryOperator( + in ApiControllerSymbolCache symbolCache, + IOperation operation, + IOperation otherOperation, + bool expectedConstantValue) + { + if (operation.Kind != OperationKind.Literal) + { + return false; + } + + var constantValue = ((ILiteralOperation)operation).ConstantValue; + if (!constantValue.HasValue || + !(constantValue.Value is bool boolCostantVaue) || + boolCostantVaue != expectedConstantValue) + { + return false; + } + + return IsModelStateIsValidPropertyAccessor(symbolCache, otherOperation); + } + + private static bool IsModelStateIsValidPropertyAccessor(in ApiControllerSymbolCache symbolCache, IOperation operation) + { + if (operation.Kind != OperationKind.PropertyReference) + { + return false; + } + + var propertyReference = (IPropertyReferenceOperation)operation; + if (propertyReference.Property.Name != "IsValid") + { + return false; + } + + if (propertyReference.Member.ContainingType != symbolCache.ModelStateDictionary) + { + return false; + } + + if (propertyReference.Instance?.Kind != OperationKind.PropertyReference) + { + // Verify this is refering to the ModelState property on the current controller instance + return false; + } + + var modelStatePropertyReference = (IPropertyReferenceOperation)propertyReference.Instance; + if (modelStatePropertyReference.Instance?.Kind != OperationKind.InstanceReference) + { + return false; + } + + return true; + } + + private static IOperation UnwrapSingleStatementBlock(IOperation statement) + { + return statement is IBlockOperation block && block.Operations.Length == 1 ? + block.Operations[0] : + statement; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs new file mode 100644 index 0000000000..3446de28a4 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs @@ -0,0 +1,69 @@ +// 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.Immutable; +using System.Composition; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CodeFixes; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Text; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider : CodeFixProvider + { + public sealed override ImmutableArray FixableDiagnosticIds => + ImmutableArray.Create(DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck.Id); + + public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + if (context.Diagnostics.Length != 1) + { + return Task.CompletedTask; + } + + var diagnostic = context.Diagnostics[0]; + if (diagnostic.Id != DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck.Id) + { + return Task.CompletedTask; + } + + context.RegisterCodeFix(new MyCodeAction(context.Document, context.Span), diagnostic); + return Task.CompletedTask; + } + + private class MyCodeAction : CodeAction + { + private readonly Document _document; + private readonly TextSpan _ifBlockSpan; + + public MyCodeAction(Document document, TextSpan ifBlockSpan) + { + _document = document; + _ifBlockSpan = ifBlockSpan; + } + + public override string EquivalenceKey => Title; + + public override string Title => "Remove ModelState.IsValid check"; + + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var rootNode = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var editor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); + + var ifBlockSyntax = rootNode.FindNode(_ifBlockSpan); + editor.RemoveNode(ifBlockSyntax); + + return editor.GetChangedDocument(); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs new file mode 100644 index 0000000000..48d81975ef --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs @@ -0,0 +1,37 @@ +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal static class ApiControllerFacts + { + public static bool IsApiControllerAction(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + { + if (method == null) + { + return false; + } + + if (method.ReturnsVoid || method.ReturnType.TypeKind == TypeKind.Error) + { + return false; + } + + if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute)) + { + return false; + } + + if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) + { + return false; + } + + if (!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose)) + { + return false; + } + + return true; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs index 74e687e710..a28cee2458 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs @@ -20,6 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers IActionResult = compilation.GetTypeByMetadataName(SymbolNames.IActionResult); IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata); IConvertToActionResult = compilation.GetTypeByMetadataName(SymbolNames.IConvertToActionResult); + ModelStateDictionary = compilation.GetTypeByMetadataName(SymbolNames.ModelStateDictionary); NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); @@ -51,6 +52,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public IMethodSymbol IDisposableDispose { get; } + public ITypeSymbol ModelStateDictionary { get; } + public INamedTypeSymbol NonActionAttribute { get; } public INamedTypeSymbol NonControllerAttribute { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs index 57ce1b1e26..30b4b507d8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var semanticModel = syntaxNodeContext.SemanticModel; var method = semanticModel.GetDeclaredSymbol(methodSyntax, syntaxNodeContext.CancellationToken); - if (!ShouldEvaluateMethod(symbolCache, method)) + if (!ApiControllerFacts.IsApiControllerAction(symbolCache, method)) { return; } @@ -113,36 +113,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return attributes; } - internal static bool ShouldEvaluateMethod(ApiControllerSymbolCache symbolCache, IMethodSymbol method) - { - if (method == null) - { - return false; - } - - if (method.ReturnsVoid || method.ReturnType.TypeKind == TypeKind.Error) - { - return false; - } - - if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute)) - { - return false; - } - - if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) - { - return false; - } - - if (!MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose)) - { - return false; - } - - return true; - } - internal static bool HasStatusCode(IList declaredApiResponseMetadata, int statusCode) { if (declaredApiResponseMetadata.Count == 0) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs index ea5e213385..f8c1b7bb72 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs @@ -69,5 +69,16 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers "Usage", DiagnosticSeverity.Info, isEnabledByDefault: false); + + public static readonly DiagnosticDescriptor MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck = + new DiagnosticDescriptor( + "MVC1007", + "Action methods on ApiController instances do not require explicit model validation check.", + "Action methods on ApiController instances do not require explicit model validation check.", + "Usage", + DiagnosticSeverity.Info, + isEnabledByDefault: true, + customTags: new[] { WellKnownDiagnosticTags.Unnecessary }); + } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index 32b7cf62f3..db0874e74e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -23,6 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; + public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; + public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; @@ -31,12 +33,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string IFilterMetadataType = "Microsoft.AspNetCore.Mvc.Filters.IFilterMetadata"; - public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; - public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; + public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; + public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; public const string NonControllerAttribute = "Microsoft.AspNetCore.Mvc.NonControllerAttribute"; diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs deleted file mode 100644 index 85c805bca9..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApActionsDoNotRequireExplicitModelValidationCheckFacts.cs +++ /dev/null @@ -1,359 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiActionsDoNotRequireExplicitModelValidationCheckFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7001_ApiActionsHaveBadModelStateFilter; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeIsNotApiController() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public IActionResult Index() => View(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenActionDoesNotHaveModelStateCheck() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - public IActionResult GetPetId() - { - return Ok(new object()); - } -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenAActionsUseExpressionBodies() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - public IActionResult GetPetId() => ModelState.IsVald ? OK() : BadResult(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonActions() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - private int GetPetIdPrivate() => 0; - protected int GetPetIdProtected() => 0; - public static IActionResult FindPetByStatus(int status) => null; - [NonAction] - public object Reset(int state) => null; -}"; - - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public Task DiagnosticsAndCodeFixes_WhenActionHasModelStateIsValidCheck() - { - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (!ModelState.IsValid) - { - return BadRequest(); - } - - return Ok(); - } -}"; - - // Act & Assert - return VerifyAsync(test); - } - - - [Fact] - public Task DiagnosticsAndCodeFixes_WhenActionHasModelStateIsValidCheck_UsingComparisonToFalse() - { - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (ModelState.IsValid == false) - { - return BadRequest(); - } - - return Ok(); - } -}"; - - // Act & Assert - return VerifyAsync(test); - } - - [Fact] - public Task DiagnosticsAndCodeFixes_WhenActionHasModelStateIsValidCheck_WithoutBraces() - { - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (!ModelState.IsValid) - return BadRequest(); - - return Ok(); - } -}"; - return VerifyAsync(test); - } - - [Fact] - public async Task DiagnosticsAndCodeFixes_WhenModelStateIsInElseIf() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 13, 9); - - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (User == null) - { - return Unauthorized(); - } - else if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - return Ok(); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (User == null) - { - return Unauthorized(); - } - - return Ok(); - } -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task DiagnosticsAndCodeFixes_WhenModelStateIsInNestedBlock() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 15, 13); - - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (User == null) - { - return Unauthorized(); - } - else - { - if (!ModelState.IsValid) - { - return BadRequest(ModelState); - } - - Debug.Assert(ModelState.Count == 0); - } - - return Ok(); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - if (User == null) - { - return Unauthorized(); - } - else - { - Debug.Assert(ModelState.Count == 0); - } - - return Ok(); - } -}"; - - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private async Task VerifyAsync(string test) - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 9, 9); - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : ControllerBase -{ - public IActionResult GetPetId() - { - return Ok(); - } -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} \ No newline at end of file diff --git a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs b/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs new file mode 100644 index 0000000000..d2feb871ff --- /dev/null +++ b/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs @@ -0,0 +1,86 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest + { + private MvcDiagnosticAnalyzerRunner AnalyzerRunner { get; } = new MvcDiagnosticAnalyzerRunner(new ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer()); + + [Fact] + public Task NoDiagnosticsAreReturned_ForNonApiController() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForRazorPageModels() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task DiagnosticsAreReturned_ForApiActionsWithModelStateChecks() + => RunTest(); + + [Fact] + public Task DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality() + => RunTest(); + + [Fact] + public Task DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing() + => RunTest(); + + private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await AnalyzerRunner.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Empty(result); + } + + private async Task RunTest([CallerMemberName] string testMethod = "") + { + // Arrange + var descriptor = DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck; + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await AnalyzerRunner.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Collection( + result, + diagnostic => + { + Assert.Equal(descriptor.Id, diagnostic.Id); + Assert.Same(descriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + }); + } + } +} diff --git a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs b/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs new file mode 100644 index 0000000000..d470e22112 --- /dev/null +++ b/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs @@ -0,0 +1,63 @@ +// 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.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest + { + private MvcDiagnosticAnalyzerRunner AnalyzerRunner { get; } = new MvcDiagnosticAnalyzerRunner(new ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer()); + + private CodeFixRunner CodeFixRunner => CodeFixRunner.Default; + + [Fact] + public Task CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck() + => RunTest(); + + [Fact] + public Task CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck() + => RunTest(); + + [Fact] + public Task CodeFixRemovesIfBlockWithoutBraces() + => RunTest(); + + private async Task RunTest([CallerMemberName] string testMethod = "") + { + // Arrange + var project = GetProject(testMethod); + var controllerDocument = project.DocumentIds[0]; + var expectedOutput = Read(testMethod + ".Output"); + + // Act + var diagnostics = await AnalyzerRunner.GetDiagnosticsAsync(project); + Assert.NotEmpty(diagnostics); + var actualOutput = await CodeFixRunner.ApplyCodeFixAsync( + new ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider(), + project.GetDocument(controllerDocument), + diagnostics[0]); + + Assert.Equal(expectedOutput, actualOutput, ignoreLineEndingDifferences: true); + } + + private Project GetProject(string testMethod) + { + var testSource = Read(testMethod + ".Input"); + return DiagnosticProject.Create(GetType().Assembly, new[] { testSource }); + } + + private string Read(string fileName) + { + return MvcTestSource.Read(GetType().Name, fileName) + .Source + .Replace("_INPUT_", "_TEST_") + .Replace("_OUTPUT_", "_TEST_"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs b/test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs similarity index 78% rename from test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs rename to test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs index b1e7578557..a264d33878 100644 --- a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerTest.cs +++ b/test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs @@ -10,10 +10,10 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Analyzers { - public class ApiConventionAnalyzerTest + public class ApiControllerFactsTest { [Fact] - public async Task ShouldEvaluateMethod_ReturnsFalse_IfMethodReturnTypeIsInvalid() + public async Task IsApiControllerAction_ReturnsFalse_IfMethodReturnTypeIsInvalid() { // Arrange var source = @" @@ -41,14 +41,14 @@ namespace TestNamespace var method = (IMethodSymbol)compilation.GetTypeByMetadataName("TestNamespace.TestController").GetMembers("Get").First(); // Act - var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); // Assert Assert.False(result); } [Fact] - public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotController() + public async Task IsApiControllerAction_ReturnsFalse_IfContainingTypeIsNotController() { // Arrange var compilation = await GetCompilation(); @@ -57,14 +57,14 @@ namespace TestNamespace var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_IndexModel.OnGet)).First(); // Act - var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); // Assert Assert.False(result); } [Fact] - public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotApiController() + public async Task IsApiControllerAction_ReturnsFalse_IfContainingTypeIsNotApiController() { // Arrange var compilation = await GetCompilation(); @@ -73,14 +73,14 @@ namespace TestNamespace var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_NotApiController.Index)).First(); // Act - var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); // Assert Assert.False(result); } [Fact] - public async Task ShouldEvaluateMethod_ReturnsFalse_IfContainingTypeIsNotAction() + public async Task IsApiControllerAction_ReturnsFalse_IfContainingTypeIsNotAction() { // Arrange var compilation = await GetCompilation(); @@ -89,14 +89,14 @@ namespace TestNamespace var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_NotAction.Index)).First(); // Act - var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); // Assert Assert.False(result); } [Fact] - public async Task ShouldEvaluateMethod_ReturnsTrue_ForValidActionMethods() + public async Task IsApiControllerAction_ReturnsTrue_ForValidActionMethods() { // Arrange var compilation = await GetCompilation(); @@ -105,7 +105,7 @@ namespace TestNamespace var method = (IMethodSymbol)type.GetMembers(nameof(ApiConventionAnalyzerTest_Valid.Index)).First(); // Act - var result = ApiConventionAnalyzer.ShouldEvaluateMethod(symbolCache, method); + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); // Assert Assert.True(result); @@ -113,7 +113,7 @@ namespace TestNamespace private Task GetCompilation() { - var testSource = MvcTestSource.Read(GetType().Name, "ApiConventionAnalyzerTestFile"); + var testSource = MvcTestSource.Read(GetType().Name, "TestFile"); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); return project.GetCompilationAsync(); diff --git a/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs b/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs index d3a8d94b73..52a18845f0 100644 --- a/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs +++ b/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs @@ -22,5 +22,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure { return GetDiagnosticsAsync(sources: new[] { source }, Analyzer, Array.Empty()); } + + public Task GetDiagnosticsAsync(Project project) + { + return GetDiagnosticsAsync(new[] { project }, Analyzer, Array.Empty()); + } } } diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs new file mode 100644 index 0000000000..1c60c0d9ee --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs @@ -0,0 +1,22 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class DiagnosticsAreReturned_ForApiActionsWithModelStateChecks : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + /*MM*/if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs new file mode 100644 index 0000000000..b4056af443 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs @@ -0,0 +1,22 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 1) + { + return NotFound(); + } + + /*MM*/if (ModelState.IsValid == false) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs new file mode 100644 index 0000000000..26b8712711 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + return BadRequest(); + + /*MM*/if (!ModelState.IsValid) + return BadRequest(); + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs new file mode 100644 index 0000000000..056ba8a266 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions : ControllerBase + { + public IActionResult Method(int id) + { + if (!ModelState.IsValid && !Request.Query.ContainsKey("skip-validation")) + { + return UnprocessableEntity(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs new file mode 100644 index 0000000000..32cf061473 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs new file mode 100644 index 0000000000..e08e3e9489 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + [Route("/api/[controller]")] + public class NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock : ControllerBase + { + public IActionResult Method(int id) + { + if (!ModelState.IsValid) + { + return UnprocessableEntity(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs new file mode 100644 index 0000000000..b60f20b7a7 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs @@ -0,0 +1,16 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs new file mode 100644 index 0000000000..abc6b48359 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs @@ -0,0 +1,15 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + public class NoDiagnosticsAreReturned_ForNonApiController : ControllerBase + { + public IActionResult Method(int id) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs new file mode 100644 index 0000000000..4537050f31 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs @@ -0,0 +1,17 @@ +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +{ + public class Home : PageModel + { + public IActionResult OnPost(int id) + { + if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Page(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs new file mode 100644 index 0000000000..b3c31d8536 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesIfBlockWithoutBraces : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + return BadRequest(); + + if (!ModelState.IsValid) + return BadRequest(); + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs new file mode 100644 index 0000000000..1b5bd73c47 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesIfBlockWithoutBraces : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + return BadRequest(); + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs new file mode 100644 index 0000000000..6d1d9db2f5 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs @@ -0,0 +1,22 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + if (ModelState.IsValid == false) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs new file mode 100644 index 0000000000..8d61173949 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs new file mode 100644 index 0000000000..0a1071c29c --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs @@ -0,0 +1,22 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + if (!ModelState.IsValid) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs new file mode 100644 index 0000000000..1d82b016e9 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +{ + [ApiController] + [Route("/api/[controller]")] + public class CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck : ControllerBase + { + public IActionResult Method(int id) + { + if (id == 0) + { + return BadRequest(); + } + + return Ok(); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerTest/ApiConventionAnalyzerTestFile.cs rename to test/Mvc.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs From b2a1a7c9def3abf002f36a24616ae9f5592811ff Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 30 Jul 2018 14:59:25 -0700 Subject: [PATCH 143/316] Ensure parent is a MethodDeclarationSyntax --- ...tionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs index bdd8dbac75..0408513ced 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -74,7 +74,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return; } - var methodSyntax = (MethodDeclarationSyntax)parent.Syntax; + if (!(parent.Syntax is MethodDeclarationSyntax methodSyntax)) + { + return; + } + var semanticModel = operationAnalysisContext.Compilation.GetSemanticModel(methodSyntax.SyntaxTree); var methodSymbol = semanticModel.GetDeclaredSymbol(methodSyntax, operationAnalysisContext.CancellationToken); From ddde149792a9b4d6865205dfc1beb44abc0a47de Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 30 Jul 2018 15:06:43 -0700 Subject: [PATCH 144/316] Cleanup some IDE warnings --- .../LinkTagHelper.cs | 12 ++++-------- .../ScriptTagHelper.cs | 6 ++---- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs index 07054bed8a..2d3b8b9456 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs @@ -257,8 +257,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // not function properly. Href = output.Attributes[HrefAttributeName]?.Value as string; - Mode mode; - if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out mode)) + if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out var mode)) { // No attributes matched so we have nothing to do return; @@ -295,8 +294,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers if (mode == Mode.Fallback && HasStyleSheetLinkType(output.Attributes)) { - string resolvedUrl; - if (TryResolveUrl(FallbackHref, resolvedUrl: out resolvedUrl)) + if (TryResolveUrl(FallbackHref, resolvedUrl: out string resolvedUrl)) { FallbackHref = resolvedUrl; } @@ -395,17 +393,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private bool HasStyleSheetLinkType(TagHelperAttributeList attributes) { - TagHelperAttribute relAttribute; - if (!attributes.TryGetAttribute(RelAttributeName, out relAttribute) || + if (!attributes.TryGetAttribute(RelAttributeName, out var relAttribute) || relAttribute.Value == null) { return false; } var attributeValue = relAttribute.Value; - var contentValue = attributeValue as IHtmlContent; var stringValue = attributeValue as string; - if (contentValue != null) + if (attributeValue is IHtmlContent contentValue) { contentValue.WriteTo(StringWriter, HtmlEncoder); stringValue = StringWriter.ToString(); diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs index 3d0ca2d33f..15f7323db3 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs @@ -221,8 +221,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // not function properly. Src = output.Attributes[SrcAttributeName]?.Value as string; - Mode mode; - if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out mode)) + if (!AttributeMatcher.TryDetermineMode(context, ModeDetails, Compare, out var mode)) { // No attributes matched so we have nothing to do return; @@ -259,8 +258,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers if (mode == Mode.Fallback) { - string resolvedUrl; - if (TryResolveUrl(FallbackSrc, resolvedUrl: out resolvedUrl)) + if (TryResolveUrl(FallbackSrc, resolvedUrl: out string resolvedUrl)) { FallbackSrc = resolvedUrl; } From 0726b8b98b9741e5b611e690dfc88646b1226881 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 16 Jul 2018 13:39:49 -0700 Subject: [PATCH 145/316] Make publicly exposed Roslyn types internal --- .../Compilation/MetadataReferenceFeature.cs | 2 + .../MetadataReferenceFeatureProvider.cs | 1 + .../Compilation/RazorReferenceManager.cs | 2 + .../MvcRazorMvcCoreBuilderExtensions.cs | 4 ++ .../Internal/CSharpCompiler.cs | 4 ++ .../Internal/DefaultRazorReferenceManager.cs | 6 +++ .../Internal/LazyMetadataReferenceFeature.cs | 4 ++ .../Internal/RazorViewCompilerProvider.cs | 2 + .../RazorViewEngineOptions.cs | 2 + .../MetadataReferenceFeatureProviderTest.cs | 2 + .../MvcRazorMvcCoreBuilderExtensionsTest.cs | 10 +++- .../Internal/CSharpCompilerTest.cs | 49 +++++++++---------- .../DefaultRazorReferenceManagerTest.cs | 2 + .../Internal/RazorViewCompilerTest.cs | 6 +++ .../MvcServiceCollectionExtensionsTest.cs | 2 + ...ssemblyMetadataReferenceFeatureProvider.cs | 2 + 16 files changed, 72 insertions(+), 28 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs index 7ad99a2afe..478d21eed8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs @@ -1,6 +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 System; using System.Collections.Generic; using Microsoft.CodeAnalysis; @@ -9,6 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation /// /// Specifies the list of used in Razor compilation. /// + [Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")] public class MetadataReferenceFeature { /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs index f5a42ace01..8050671a1f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs @@ -17,6 +17,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation /// uses for registered instances to create /// . /// + [Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")] public class MetadataReferenceFeatureProvider : IApplicationFeatureProvider { /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs index 5b11ce10e3..df09623169 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs @@ -1,6 +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 System; using System.Collections.Generic; using Microsoft.CodeAnalysis; @@ -9,6 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation /// /// Manages compilation references for Razor compilation. /// + [Obsolete("This type is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")] public abstract class RazorReferenceManager { /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 7dddf090bd..236eb88fa2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -65,10 +65,12 @@ namespace Microsoft.Extensions.DependencyInjection private static void AddRazorViewEngineFeatureProviders(IMvcCoreBuilder builder) { +#pragma warning disable CS0618 // Type or member is obsolete if (!builder.PartManager.FeatureProviders.OfType().Any()) { builder.PartManager.FeatureProviders.Add(new MetadataReferenceFeatureProvider()); } +#pragma warning restore CS0618 // Type or member is obsolete if (!builder.PartManager.FeatureProviders.OfType().Any()) { @@ -146,7 +148,9 @@ namespace Microsoft.Extensions.DependencyInjection internal static void AddRazorViewEngineServices(IServiceCollection services) { services.TryAddSingleton(); +#pragma warning disable CS0618 // Type or member is obsolete services.TryAddSingleton(); +#pragma warning restore CS0618 // Type or member is obsolete services.TryAddEnumerable( ServiceDescriptor.Transient, MvcRazorMvcViewOptionsSetup>()); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs index 93703d49c9..496d2f9ee7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs @@ -19,7 +19,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class CSharpCompiler { +#pragma warning disable CS0618 // Type or member is obsolete private readonly RazorReferenceManager _referenceManager; +#pragma warning restore CS0618 // Type or member is obsolete private readonly IHostingEnvironment _hostingEnvironment; private bool _optionsInitialized; private CSharpParseOptions _parseOptions; @@ -27,7 +29,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private EmitOptions _emitOptions; private bool _emitPdb; +#pragma warning disable CS0618 // Type or member is obsolete public CSharpCompiler(RazorReferenceManager manager, IHostingEnvironment hostingEnvironment) +#pragma warning restore CS0618 // Type or member is obsolete { _referenceManager = manager ?? throw new ArgumentNullException(nameof(manager)); _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs index f1d59ef770..04965e52a4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs @@ -11,7 +11,9 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Razor.Internal { +#pragma warning disable CS0618 // Type or member is obsolete public class DefaultRazorReferenceManager : RazorReferenceManager +#pragma warning restore CS0618 // Type or member is obsolete { private readonly ApplicationPartManager _partManager; private readonly IList _additionalMetadataReferences; @@ -24,7 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal IOptions optionsAccessor) { _partManager = partManager; +#pragma warning disable CS0618 // Type or member is obsolete _additionalMetadataReferences = optionsAccessor.Value.AdditionalCompilationReferences; +#pragma warning restore CS0618 // Type or member is obsolete } public override IReadOnlyList CompilationReferences @@ -41,7 +45,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private IReadOnlyList GetCompilationReferences() { +#pragma warning disable CS0618 // Type or member is obsolete var feature = new MetadataReferenceFeature(); +#pragma warning restore CS0618 // Type or member is obsolete _partManager.PopulateFeature(feature); var applicationReferences = feature.MetadataReferences; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs index ead4b29630..5479991463 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs @@ -11,9 +11,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class LazyMetadataReferenceFeature : IMetadataReferenceFeature { +#pragma warning disable CS0618 // Type or member is obsolete private readonly RazorReferenceManager _referenceManager; +#pragma warning restore CS0618 // Type or member is obsolete +#pragma warning disable CS0618 // Type or member is obsolete public LazyMetadataReferenceFeature(RazorReferenceManager referenceManager) +#pragma warning restore CS0618 // Type or member is obsolete { _referenceManager = referenceManager; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs index 4ef67017d1..560b1493d6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs @@ -75,7 +75,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _fileProviderAccessor.FileProvider, _razorProjectEngine, _csharpCompiler, +#pragma warning disable CS0618 // Type or member is obsolete _viewEngineOptions.CompilationCallback, +#pragma warning restore CS0618 // Type or member is obsolete feature.ViewDescriptors, _compilationMemoryCacheProvider.CompilationMemoryCache, _logger); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs index 532c924d32..8ae0120211 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs @@ -157,6 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// Gets the instances that should be included in Razor compilation, along with /// those discovered by s. /// + [Obsolete("This property is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")] public IList AdditionalCompilationReferences { get; } = new List(); /// @@ -166,6 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// /// Customizations made here would not reflect in tooling (Intellisense). /// + [Obsolete("This property is obsolete and will be removed in a future version. See https://aka.ms/AA1x4gg for details.")] public Action CompilationCallback { get => _compilationCallback; diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs index fe05fdb561..8f9653c70d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs @@ -8,6 +8,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Compilation { +#pragma warning disable CS0618 // Type or member is obsolete public class MetadataReferenceFeatureProviderTest { [Fact] @@ -48,4 +49,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation reference => reference.Display.Equals(currentAssembly.Location)); } } +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs index 6ea2e2afb5..bb0ae3360b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs @@ -66,7 +66,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection builder.AddRazorViewEngine(); // Assert +#pragma warning disable CS0618 // Type or member is obsolete Assert.Single(builder.PartManager.FeatureProviders.OfType()); +#pragma warning restore CS0618 // Type or member is obsolete } [Fact] @@ -83,7 +85,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection builder.AddRazorViewEngine(); // Assert +#pragma warning disable CS0618 // Type or member is obsolete Assert.Single(builder.PartManager.FeatureProviders.OfType()); +#pragma warning restore CS0618 // Type or member is obsolete } [Fact] @@ -92,7 +96,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection // Arrange var services = new ServiceCollection(); var builder = services.AddMvcCore(); +#pragma warning disable CS0618 // Type or member is obsolete var metadataReferenceFeatureProvider = new MetadataReferenceFeatureProvider(); +#pragma warning restore CS0618 // Type or member is obsolete builder.PartManager.FeatureProviders.Add(metadataReferenceFeatureProvider); // Act @@ -100,7 +106,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.DependencyInjection // Assert var actual = Assert.Single( - builder.PartManager.FeatureProviders.OfType()); +#pragma warning disable CS0618 // Type or member is obsolete + collection: builder.PartManager.FeatureProviders.OfType()); +#pragma warning restore CS0618 // Type or member is obsolete Assert.Same(metadataReferenceFeatureProvider, actual); } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs index 3faddaa0e6..8c3440db2a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs @@ -17,6 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { public class CSharpCompilerTest { +#pragma warning disable CS0618 // Type or member is obsolete + private readonly RazorReferenceManager ReferenceManager = Mock.Of(); +#pragma warning restore CS0618 // Type or member is obsolete + [Theory] [InlineData(null)] [InlineData("")] @@ -24,8 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var hostingEnvironment = Mock.Of(e => e.ApplicationName == name); - var referenceManager = Mock.Of(); - var compiler = new CSharpCompiler(referenceManager, hostingEnvironment); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment); // Act var options = compiler.GetDependencyContextCompilationOptions(); @@ -41,8 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var hostingEnvironment = new Mock(); hostingEnvironment.SetupGet(e => e.ApplicationName) .Returns(typeof(Controller).GetTypeInfo().Assembly.GetName().Name); - var referenceManager = Mock.Of(); - var compiler = new CSharpCompiler(referenceManager, hostingEnvironment.Object); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object); // Act var options = compiler.GetDependencyContextCompilationOptions(); @@ -58,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var hostingEnvironment = new Mock(); hostingEnvironment.SetupGet(e => e.ApplicationName) .Returns(GetType().GetTypeInfo().Assembly.GetName().Name); - var compiler = new CSharpCompiler(Mock.Of(), hostingEnvironment.Object); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object); // Act & Assert var parseOptions = compiler.ParseOptions; @@ -78,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var hostingEnvironment = new Mock(); hostingEnvironment.SetupGet(e => e.EnvironmentName) .Returns(environment); - var compiler = new CSharpCompiler(Mock.Of(), hostingEnvironment.Object); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object); // Act & Assert var compilationOptions = compiler.CSharpCompilationOptions; @@ -96,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var hostingEnvironment = new Mock(); hostingEnvironment.SetupGet(e => e.EnvironmentName) .Returns(environment); - var compiler = new CSharpCompiler(Mock.Of(), hostingEnvironment.Object); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment.Object); // Act & Assert var parseOptions = compiler.ParseOptions; @@ -108,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var hostingEnvironment = Mock.Of(h => h.EnvironmentName == "Development"); - var compiler = new CSharpCompiler(Mock.Of(), hostingEnvironment); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment); // Act & Assert var compilationOptions = compiler.CSharpCompilationOptions; @@ -138,7 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { // Arrange var hostingEnvironment = Mock.Of(h => h.EnvironmentName == "Development"); - var compiler = new CSharpCompiler(Mock.Of(), hostingEnvironment); + var compiler = new CSharpCompiler(ReferenceManager, hostingEnvironment); // Act & Assert var parseOptions = compiler.ParseOptions; @@ -163,10 +165,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var compilationOptions = compiler.ParseOptions; @@ -191,10 +192,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: "portable", emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var emitOptions = compiler.EmitOptions; @@ -219,10 +219,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: "embedded", emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var emitOptions = compiler.EmitOptions; @@ -247,10 +246,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: "none", emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert Assert.False(compiler.EmitPdb); @@ -273,10 +271,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var compilationOptions = compiler.CSharpCompilationOptions; @@ -300,10 +297,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var compilationOptions = compiler.CSharpCompilationOptions; @@ -327,10 +323,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var compilationOptions = compiler.CSharpCompilationOptions; @@ -354,10 +349,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act & Assert var parseOptions = compiler.ParseOptions; @@ -383,9 +377,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal debugType: null, emitEntryPoint: null, generateXmlDocumentation: null); - var referenceManager = Mock.Of(); var hostingEnvironment = Mock.Of(); - var compiler = new TestCSharpCompiler(referenceManager, hostingEnvironment, dependencyContextOptions); + var compiler = new TestCSharpCompiler(ReferenceManager, hostingEnvironment, dependencyContextOptions); // Act var syntaxTree = compiler.CreateSyntaxTree(SourceText.From(content)); @@ -399,7 +392,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly DependencyContextCompilationOptions _options; public TestCSharpCompiler( +#pragma warning disable CS0618 // Type or member is obsolete RazorReferenceManager referenceManager, +#pragma warning restore CS0618 // Type or member is obsolete IHostingEnvironment hostingEnvironment, DependencyContextCompilationOptions options) : base(referenceManager, hostingEnvironment) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs index dc8cd4a3d0..c6435cd36e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs @@ -12,6 +12,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal { +#pragma warning disable CS0618 // Type or member is obsolete public class DefaultRazorReferenceManagerTest { [Fact] @@ -52,4 +53,5 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Test.Internal return applicationPartManager; } } +#pragma warning restore CS0618 // Type or member is obsolete } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs index 21840806ba..0c68fed85f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs @@ -824,7 +824,9 @@ this should fail"; private static TestRazorViewCompiler GetViewCompiler( TestFileProvider fileProvider = null, Action compilationCallback = null, +#pragma warning disable CS0618 // Type or member is obsolete RazorReferenceManager referenceManager = null, +#pragma warning restore CS0618 // Type or member is obsolete IList precompiledViews = null, CSharpCompiler csharpCompiler = null) { @@ -858,6 +860,7 @@ this should fail"; return viewCompiler; } +#pragma warning disable CS0618 // Type or member is obsolete private static RazorReferenceManager CreateReferenceManager(IOptions options) { var applicationPartManager = new ApplicationPartManager(); @@ -867,6 +870,7 @@ this should fail"; return new DefaultRazorReferenceManager(applicationPartManager, options); } +#pragma warning restore CS0618 // Type or member is obsolete private class TestRazorViewCompiler : RazorViewCompiler { @@ -900,7 +904,9 @@ this should fail"; private class TestCSharpCompiler : CSharpCompiler { +#pragma warning disable CS0618 // Type or member is obsolete public TestCSharpCompiler(RazorReferenceManager manager, IHostingEnvironment hostingEnvironment) +#pragma warning restore CS0618 // Type or member is obsolete : base(manager, hostingEnvironment) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index a9fa6f7d9c..f2700f2a97 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -214,7 +214,9 @@ namespace Microsoft.AspNetCore.Mvc Assert.Collection(manager.FeatureProviders, feature => Assert.IsType(feature), feature => Assert.IsType(feature), +#pragma warning disable CS0618 // Type or member is obsolete feature => Assert.IsType(feature), +#pragma warning restore CS0618 // Type or member is obsolete feature => Assert.IsType(feature), feature => Assert.IsType(feature), #pragma warning disable CS0618 // Type or member is obsolete diff --git a/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs b/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs index 99c96ceb38..63f1877a45 100644 --- a/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs +++ b/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs @@ -9,6 +9,7 @@ using Microsoft.CodeAnalysis; namespace ControllersFromServicesWebSite { +#pragma warning disable CS0618 // Type or member is obsolete public class AssemblyMetadataReferenceFeatureProvider : IApplicationFeatureProvider { public void PopulateFeature(IEnumerable parts, MetadataReferenceFeature feature) @@ -17,4 +18,5 @@ namespace ControllersFromServicesWebSite feature.MetadataReferences.Add(MetadataReference.CreateFromFile(currentAssembly.Location)); } } +#pragma warning restore CS0618 // Type or member is obsolete } From 046af405b6f957a51910534f0c2e5dcb5e4a0e8e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 1 Aug 2018 15:05:49 +1200 Subject: [PATCH 146/316] Rename global routing to endpoint routing (#8179) --- .../MvcApplicationBuilderExtensions.cs | 8 ++++---- ...MvcOptionsConfigureCompatibilityOptions.cs | 2 +- .../MvcOptions.cs | 19 +++++++++++++------ ...lHelper.cs => EndpointRoutingUrlHelper.cs} | 10 +++++----- .../Routing/UrlHelperFactory.cs | 4 ++-- .../MvcApplicationBuilderExtensionsTest.cs | 8 ++++---- ...est.cs => EndpointRoutingUrlHelperTest.cs} | 4 ++-- ... ConsumesAttributeEndpointRoutingTests.cs} | 4 ++-- ...ngTests.cs => CorsEndpointRoutingTests.cs} | 4 ++-- ...lRoutingTest.cs => EndpointRoutingTest.cs} | 4 ++-- ... => RequestServicesEndpointRoutingTest.cs} | 4 ++-- ...s.cs => VersioningEndpointRoutingTests.cs} | 4 ++-- .../CompatibilitySwitchIntegrationTest.cs | 8 ++++---- test/WebSites/BasicWebSite/Startup.cs | 2 +- ...uting.cs => StartupWithEndpointRouting.cs} | 4 +--- ...uting.cs => StartupWithEndpointRouting.cs} | 4 ++-- ...uting.cs => StartupWithEndpointRouting.cs} | 4 ++-- ...uting.cs => StartupWithEndpointRouting.cs} | 4 ++-- 18 files changed, 53 insertions(+), 48 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Core/Routing/{GlobalRoutingUrlHelper.cs => EndpointRoutingUrlHelper.cs} (94%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/{GlobalRoutingUrlHelperTest.cs => EndpointRoutingUrlHelperTest.cs} (99%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{ConsumesAttributeGlobalRoutingTests.cs => ConsumesAttributeEndpointRoutingTests.cs} (77%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{CorsDispatchingTests.cs => CorsEndpointRoutingTests.cs} (84%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{GlobalRoutingTest.cs => EndpointRoutingTest.cs} (96%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{RequestServicesGlobalRoutingTest.cs => RequestServicesEndpointRoutingTest.cs} (78%) rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{VersioningGlobalRoutingTests.cs => VersioningEndpointRoutingTests.cs} (92%) rename test/WebSites/BasicWebSite/{StartupWithGlobalRouting.cs => StartupWithEndpointRouting.cs} (94%) rename test/WebSites/CorsWebSite/{StartupWithGlobalRouting.cs => StartupWithEndpointRouting.cs} (95%) rename test/WebSites/RoutingWebSite/{StartupWithGlobalRouting.cs => StartupWithEndpointRouting.cs} (91%) rename test/WebSites/VersioningWebSite/{StartupWithGlobalRouting.cs => StartupWithEndpointRouting.cs} (86%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 0b190b0231..ab4b465492 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -18,8 +18,8 @@ namespace Microsoft.AspNetCore.Builder /// public static class MvcApplicationBuilderExtensions { - // Property key set in routing package by UseGlobalRouting to indicate middleware is registered - private const string GlobalRoutingRegisteredKey = "__GlobalRoutingMiddlewareRegistered"; + // Property key set in routing package by UseEndpointRouting to indicate middleware is registered + private const string EndpointRoutingRegisteredKey = "__EndpointRoutingMiddlewareRegistered"; /// /// Adds MVC to the request execution pipeline. @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Builder var options = app.ApplicationServices.GetRequiredService>(); - if (options.Value.EnableGlobalRouting) + if (options.Value.EnableEndpointRouting) { var mvcEndpointDataSource = app.ApplicationServices .GetRequiredService>() @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Builder } } - if (!app.Properties.TryGetValue(GlobalRoutingRegisteredKey, out _)) + if (!app.Properties.TryGetValue(EndpointRoutingRegisteredKey, out _)) { // Matching middleware has not been registered yet // For back-compat register middleware so an endpoint is matched and then immediately used diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index 28fd0605e7..76e3a16916 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure if (Version >= CompatibilityVersion.Version_2_2) { - values[nameof(MvcOptions.EnableGlobalRouting)] = true; + values[nameof(MvcOptions.EnableEndpointRouting)] = true; } return values; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index e87ddb2678..b3fa3e3745 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc private readonly CompatibilitySwitch _allowValidatingTopLevelNodes; private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; - private readonly CompatibilitySwitch _enableGlobalRouting; + private readonly CompatibilitySwitch _enableEndpointRouting; private readonly ICompatibilitySwitch[] _switches; /// @@ -55,7 +55,7 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes = new CompatibilitySwitch(nameof(AllowValidatingTopLevelNodes)); _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); - _enableGlobalRouting = new CompatibilitySwitch(nameof(EnableGlobalRouting)); + _enableEndpointRouting = new CompatibilitySwitch(nameof(EnableEndpointRouting)); _switches = new ICompatibilitySwitch[] { @@ -64,15 +64,22 @@ namespace Microsoft.AspNetCore.Mvc _allowValidatingTopLevelNodes, _inputFormatterExceptionPolicy, _suppressBindingUndefinedValueToEnumType, - _enableGlobalRouting, + _enableEndpointRouting, }; } - // REVIEW: Add documentation + // REVIEW: Remove once web hooks is using EnableEndpointRouting public bool EnableGlobalRouting { - get => _enableGlobalRouting.Value; - set => _enableGlobalRouting.Value = value; + get => EnableEndpointRouting; + set => EnableEndpointRouting = value; + } + + // REVIEW: Add documentation + public bool EnableEndpointRouting + { + get => _enableEndpointRouting.Value; + set => _enableEndpointRouting.Value = value; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs similarity index 94% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs index 13dd697fce..27590902e9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/GlobalRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs @@ -12,14 +12,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// An implementation of that uses to build URLs /// for ASP.NET MVC within an application. /// - internal class GlobalRoutingUrlHelper : UrlHelperBase + internal class EndpointRoutingUrlHelper : UrlHelperBase { - private readonly ILogger _logger; + private readonly ILogger _logger; private readonly LinkGenerator _linkGenerator; private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// - /// Initializes a new instance of the class using the specified + /// Initializes a new instance of the class using the specified /// . /// /// The for the current request. @@ -28,11 +28,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// /// The used to generate the link. /// The . - public GlobalRoutingUrlHelper( + public EndpointRoutingUrlHelper( ActionContext actionContext, IEndpointFinder routeValuesBasedEndpointFinder, LinkGenerator linkGenerator, - ILogger logger) + ILogger logger) : base(actionContext) { if (linkGenerator == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index 6db7f35638..b6c5acfdf4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -53,9 +53,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing var services = httpContext.RequestServices; var linkGenerator = services.GetRequiredService(); var routeValuesBasedEndpointFinder = services.GetRequiredService>(); - var logger = services.GetRequiredService>(); + var logger = services.GetRequiredService>(); - urlHelper = new GlobalRoutingUrlHelper( + urlHelper = new EndpointRoutingUrlHelper( context, routeValuesBasedEndpointFinder, linkGenerator, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index 8e4c7820f6..39fd3847db 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -58,13 +58,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder } [Fact] - public void UseMvc_GlobalRoutingDisabled_NoEndpointInfos() + public void UseMvc_EndpointRoutingDisabled_NoEndpointInfos() { // Arrange var services = new ServiceCollection(); services.AddSingleton(new DiagnosticListener("Microsoft.AspNetCore")); services.AddLogging(); - services.AddMvcCore(o => o.EnableGlobalRouting = false); + services.AddMvcCore(o => o.EnableEndpointRouting = false); var serviceProvider = services.BuildServiceProvider(); var appBuilder = new ApplicationBuilder(serviceProvider); @@ -85,13 +85,13 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder } [Fact] - public void UseMvc_GlobalRoutingEnabled_NoEndpointInfos() + public void UseMvc_EndpointRoutingEnabled_NoEndpointInfos() { // Arrange var services = new ServiceCollection(); services.AddSingleton(new DiagnosticListener("Microsoft.AspNetCore")); services.AddLogging(); - services.AddMvcCore(o => o.EnableGlobalRouting = true); + services.AddMvcCore(o => o.EnableEndpointRouting = true); var serviceProvider = services.BuildServiceProvider(); var appBuilder = new ApplicationBuilder(serviceProvider); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs similarity index 99% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index 4c59c5b104..faa0eebf0c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/GlobalRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -14,7 +14,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing { - public class GlobalRoutingUrlHelperTest : UrlHelperTestBase + public class EndpointRoutingUrlHelperTest : UrlHelperTestBase { [Fact] public void RouteUrl_WithRouteName_GeneratesUrl_UsingDefaults() @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var urlHelperFactory = httpContext.RequestServices.GetRequiredService(); var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); - Assert.IsType(urlHelper); + Assert.IsType(urlHelper); return urlHelper; } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs similarity index 77% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs index 1683864c17..7377442ac6 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs @@ -8,9 +8,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class ConsumesAttributeGlobalRoutingTests : ConsumesAttributeTestsBase + public class ConsumesAttributeEndpointRoutingTests : ConsumesAttributeTestsBase { - public ConsumesAttributeGlobalRoutingTests(MvcTestFixture fixture) + public ConsumesAttributeEndpointRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs similarity index 84% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs index f5281eee9e..b64655c4d9 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsDispatchingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs @@ -9,9 +9,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class CorsGlobalRoutingTests : CorsTestsBase + public class CorsEndpointRoutingTests : CorsTestsBase { - public CorsGlobalRoutingTests(MvcTestFixture fixture) + public CorsEndpointRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs similarity index 96% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index 6266e2b9b2..f6b03f55cf 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -10,9 +10,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class GlobalRoutingTest : RoutingTestsBase + public class EndpointRoutingTest : RoutingTestsBase { - public GlobalRoutingTest(MvcTestFixture fixture) + public EndpointRoutingTest(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs similarity index 78% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs index 0ca18c250e..91cba5aeb5 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesGlobalRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs @@ -8,9 +8,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class RequestServicesGlobalRoutingTest : RequestServicesTestBase + public class RequestServicesEndpointRoutingTest : RequestServicesTestBase { - public RequestServicesGlobalRoutingTest(MvcTestFixture fixture) + public RequestServicesEndpointRoutingTest(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs similarity index 92% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs index 69af08c14b..2aeb0d2490 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningGlobalRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs @@ -9,9 +9,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class VersioningGlobalRoutingTests : VersioningTestsBase + public class VersioningEndpointRoutingTests : VersioningTestsBase { - public VersioningGlobalRoutingTests(MvcTestFixture fixture) + public VersioningEndpointRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 78f8ddb4ae..858dcdf26d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -39,7 +39,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.AllExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(razorPagesOptions.AllowAreas); - Assert.False(mvcOptions.EnableGlobalRouting); + Assert.False(mvcOptions.EnableEndpointRouting); } [Fact] @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); - Assert.False(mvcOptions.EnableGlobalRouting); + Assert.False(mvcOptions.EnableEndpointRouting); } [Fact] @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); - Assert.True(mvcOptions.EnableGlobalRouting); + Assert.True(mvcOptions.EnableEndpointRouting); } [Fact] @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(InputFormatterExceptionPolicy.MalformedInputExceptions, mvcOptions.InputFormatterExceptionPolicy); Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); - Assert.True(mvcOptions.EnableGlobalRouting); + Assert.True(mvcOptions.EnableEndpointRouting); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index c6e406fb34..1c3218266e 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -28,7 +28,7 @@ namespace BasicWebSite options.Filters.Add(new TraceResourceFilter()); // Remove when all URL generation tests are passing - https://github.com/aspnet/Routing/issues/590 - options.EnableGlobalRouting = false; + options.EnableEndpointRouting = false; }) .SetCompatibilityVersion(CompatibilityVersion.Latest) .AddXmlDataContractSerializerFormatters(); diff --git a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs similarity index 94% rename from test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs rename to test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs index d3a540a86c..fca926df52 100644 --- a/test/WebSites/BasicWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs @@ -7,7 +7,7 @@ using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite { - public class StartupWithGlobalRouting + public class StartupWithEndpointRouting { // Set up application services public void ConfigureServices(IServiceCollection services) @@ -29,8 +29,6 @@ namespace BasicWebSite // Initializes the RequestId service for each request app.UseMiddleware(); - app.UseGlobalRouting(); - app.UseMvc(routes => { routes.MapRoute( diff --git a/test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs b/test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs similarity index 95% rename from test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs rename to test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs index f9e2a744c7..c620d62a58 100644 --- a/test/WebSites/CorsWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs @@ -9,11 +9,11 @@ using Microsoft.Extensions.DependencyInjection; namespace CorsWebSite { - public class StartupWithGlobalRouting + public class StartupWithEndpointRouting { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(options => options.EnableGlobalRouting = true); + services.AddMvc(options => options.EnableEndpointRouting = true); services.Configure(options => { options.AddPolicy( diff --git a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs b/test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs similarity index 91% rename from test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs rename to test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs index 62a84f5ecb..16bc62d81f 100644 --- a/test/WebSites/RoutingWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs @@ -7,13 +7,13 @@ using Microsoft.Extensions.DependencyInjection; namespace RoutingWebSite { - public class StartupWithGlobalRouting + public class StartupWithEndpointRouting { // Set up application services public void ConfigureServices(IServiceCollection services) { services.AddMvc() - .AddMvcOptions(options => options.EnableGlobalRouting = true); + .AddMvcOptions(options => options.EnableEndpointRouting = true); services.AddScoped(); services.AddSingleton(); diff --git a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs b/test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs similarity index 86% rename from test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs rename to test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs index 249aefabd4..9c72888db9 100644 --- a/test/WebSites/VersioningWebSite/StartupWithGlobalRouting.cs +++ b/test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs @@ -9,13 +9,13 @@ using Microsoft.Extensions.DependencyInjection; namespace VersioningWebSite { - public class StartupWithGlobalRouting + public class StartupWithEndpointRouting { public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container services.AddMvc() - .AddMvcOptions(options => options.EnableGlobalRouting = true); + .AddMvcOptions(options => options.EnableEndpointRouting = true); services.AddScoped(); services.AddSingleton(); From ab84d17bb3c455639dd9615f9d0a71b7df65a44f Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Wed, 25 Jul 2018 11:07:47 -0700 Subject: [PATCH 147/316] Unskip endpoint routing functional tests --- .../EndpointRoutingTest.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index f6b03f55cf..6eb78940f9 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -32,15 +32,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.True(result); } - [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] + [Fact] public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() { + // By design, this test cannot work in EndpointRouting world. This is because in case of old routing test + // when a link generation to an attribute routed controller with a non-existing action does not succeeed, + // the next route in the route collection is considered and since the next route in the route collection is + // a conventional area route, the old routing test succeeds. But this cannot happen in case of endpoint + // routing as the action does not exist to begin with. return Task.CompletedTask; } - [Fact(Skip = "Link generation issue in global routing. Need to fix - https://github.com/aspnet/Routing/issues/590")] + [Fact] public override Task ConventionalRoutedAction_InArea_StaysInArea() { + // By design, this test cannot work in EndpointRouting world. In old routing test a link is being generated + // to a non-existing action on a controller which is in an area. In case of endpoint routing, we cannot + // generate links as the action does not exist to begin with. return Task.CompletedTask; } From f4ff537a312ed46ab01470e70a6bab2133260f55 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 31 Jul 2018 16:47:33 -0700 Subject: [PATCH 148/316] Fix flaky test by using a different instance of contract resolver [Fixes #8175] FlakyTest: ExecuteAsync_ErrorDuringSerialization_DoesNotCloseTheBrackets --- .../JsonSerializerSettingsProvider.cs | 17 ++++++++-- .../MvcJsonOptionsExtensionsTests.cs | 31 +++++++++++++------ 2 files changed, 35 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs index 8b6761559d..00793a13e6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs @@ -15,10 +15,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters // return shared resolver by default for perf so slow reflection logic is cached once // developers can set their own resolver after the settings are returned if desired - private static readonly DefaultContractResolver SharedContractResolver = new DefaultContractResolver + private static readonly DefaultContractResolver SharedContractResolver; + + static JsonSerializerSettingsProvider() { - NamingStrategy = new CamelCaseNamingStrategy(), - }; + SharedContractResolver = CreateContractResolver(); + } /// /// Creates default . @@ -41,5 +43,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters TypeNameHandling = TypeNameHandling.None, }; } + + // To enable unit testing + internal static DefaultContractResolver CreateContractResolver() + { + return new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy(), + }; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs index b3370c312a..ab1c28fd3b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters.Json; using Newtonsoft.Json; using Newtonsoft.Json.Serialization; @@ -37,7 +38,8 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_WillNot_OverrideSpecifiedNames() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var annotatedFoo = new AnnotatedFoo() { HelloWorld = "Hello" @@ -55,7 +57,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_WillChange_PropertyNames() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); var foo = new { TestName = "TestFoo", TestValue = 10 }; var expected = "{\"testName\":\"TestFoo\",\"testValue\":10}"; @@ -70,7 +72,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_WillChangeFirstPartBeforeSeparator_InPropertyName() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); var foo = new { TestFoo_TestValue = "Test" }; var expected = "{\"testFoo_TestValue\":\"Test\"}"; @@ -85,7 +87,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_ProcessDictionaryKeys_WillChange_DictionaryKeys_IfTrue() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); var dictionary = new Dictionary { ["HelloWorld"] = 1, @@ -104,7 +106,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_ProcessDictionaryKeys_WillChangeFirstPartBeforeSeparator_InDictionaryKey_IfTrue() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: true); var dictionary = new Dictionary() { ["HelloWorld_HelloWorld"] = 1 @@ -123,7 +125,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseCamelCasing_ProcessDictionaryKeys_WillNotChangeDictionaryKeys_IfFalse() { // Arrange - var options = new MvcJsonOptions().UseCamelCasing(processDictionaryKeys: false); + var options = CreateDefaultMvcJsonOptions().UseCamelCasing(processDictionaryKeys: false); var dictionary = new Dictionary { ["HelloWorld"] = 1, @@ -142,7 +144,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseMemberCasing_WillNotChange_OverrideSpecifiedNames() { // Arrange - var options = new MvcJsonOptions().UseMemberCasing(); + var options = CreateDefaultMvcJsonOptions().UseMemberCasing(); var annotatedFoo = new AnnotatedFoo() { HelloWorld = "Hello" @@ -180,8 +182,8 @@ namespace Microsoft.Extensions.DependencyInjection public void UseMemberCasing_WillNotChange_PropertyNames() { // Arrange - var options = new MvcJsonOptions().UseMemberCasing(); - var foo = new { fooName = "Test", FooValue = "Value"}; + var options = CreateDefaultMvcJsonOptions().UseMemberCasing(); + var foo = new { fooName = "Test", FooValue = "Value" }; var expected = "{\"fooName\":\"Test\",\"FooValue\":\"Value\"}"; // Act @@ -195,7 +197,7 @@ namespace Microsoft.Extensions.DependencyInjection public void UseMemberCasing_WillNotChange_DictionaryKeys() { // Arrange - var options = new MvcJsonOptions().UseMemberCasing(); + var options = CreateDefaultMvcJsonOptions().UseMemberCasing(); var dictionary = new Dictionary() { ["HelloWorld"] = 1, @@ -239,6 +241,15 @@ namespace Microsoft.Extensions.DependencyInjection Assert.Equal(expectedMessage, actual: exception.Message); } + // NOTE: This method was created to make sure to create a different instance of contract resolver as by default + // MvcJsonOptions uses a static shared instance of resolver which when changed causes other tests to fail. + private MvcJsonOptions CreateDefaultMvcJsonOptions() + { + var options = new MvcJsonOptions(); + options.SerializerSettings.ContractResolver = JsonSerializerSettingsProvider.CreateContractResolver(); + return options; + } + private static string SerializeToJson(MvcJsonOptions options, object value) { return JsonConvert.SerializeObject( From 814a803ed85cd559804830f7bbb6782af8d01df7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 30 Jul 2018 17:47:48 -0700 Subject: [PATCH 149/316] Use local instances of MemoryCache in script, link and image tag helper --- .../MvcRazorMvcCoreBuilderExtensions.cs | 2 + .../TagHelperMemoryCacheProvider.cs | 23 +++++ .../ImageTagHelper.cs | 28 +++++- .../Internal/FileVersionProvider.cs | 56 ++++++------ .../Internal/GlobbingUrlBuilder.cs | 18 ++-- .../LinkTagHelper.cs | 34 +++++++- .../ScriptTagHelper.cs | 32 ++++++- .../ImageTagHelperTest.cs | 13 ++- .../Internal/DefaultTagHelperActivatorTest.cs | 87 +++++++++++++++++++ .../Internal/FileVersionProviderTest.cs | 38 ++++---- .../Internal/GlobbingUrlBuilderTest.cs | 18 ++++ .../LinkTagHelperTest.cs | 31 ++++--- .../ScriptTagHelperTest.cs | 31 ++++--- 13 files changed, 311 insertions(+), 100 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 236eb88fa2..fa30c8eddd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Extensions; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Rendering; @@ -226,6 +227,7 @@ namespace Microsoft.Extensions.DependencyInjection // Consumed by the Cache tag helper to cache results across the lifetime of the application. services.TryAddSingleton(); + services.TryAddSingleton(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs new file mode 100644 index 0000000000..e7deea3326 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs @@ -0,0 +1,23 @@ +// 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.Extensions.Caching.Memory; + +namespace Microsoft.AspNetCore.Mvc.Razor.Infrastructure +{ + /// + /// This API supports the MVC's infrastructure and is not intended to be used + /// directly from your code. This API may change in future releases. + /// + public sealed class TagHelperMemoryCacheProvider + { + /// + /// This API supports the MVC's infrastructure and is not intended to be used + /// directly from your code. This API may change in future releases. + /// + public IMemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions + { + SizeLimit = 10 * 1024 * 1024 // 10MB + }); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs index 587dfc3567..1bf569759d 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs @@ -4,11 +4,13 @@ using System; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.TagHelpers { @@ -36,6 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// The . /// The to use. /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version.")] public ImageTagHelper( IHostingEnvironment hostingEnvironment, IMemoryCache cache, @@ -47,6 +50,27 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Cache = cache; } + /// + /// Creates a new . + /// + /// The . + /// The . + /// The to use. + /// The . + // Decorated with ActivatorUtilitiesConstructor since we want to influence tag helper activation + // to use this constructor in the default case. + [ActivatorUtilitiesConstructor] + public ImageTagHelper( + IHostingEnvironment hostingEnvironment, + TagHelperMemoryCacheProvider cacheProvider, + HtmlEncoder htmlEncoder, + IUrlHelperFactory urlHelperFactory) + : base(urlHelperFactory, htmlEncoder) + { + HostingEnvironment = hostingEnvironment; + Cache = cacheProvider.Cache; + } + /// public override int Order => -1000; @@ -68,9 +92,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(AppendVersionAttributeName)] public bool AppendVersion { get; set; } - protected IHostingEnvironment HostingEnvironment { get; } + protected internal IHostingEnvironment HostingEnvironment { get; } - protected IMemoryCache Cache { get; } + protected internal IMemoryCache Cache { get; } /// public override void Process(TagHelperContext context, TagHelperOutput output) diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs index 19bdf34d09..a369be3189 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs @@ -69,42 +69,42 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal resolvedPath = path.Substring(0, queryStringOrFragmentStartIndex); } - Uri uri; - if (Uri.TryCreate(resolvedPath, UriKind.Absolute, out uri) && !uri.IsFile) + if (Uri.TryCreate(resolvedPath, UriKind.Absolute, out var uri) && !uri.IsFile) { // Don't append version if the path is absolute. return path; } - string value; - if (!_cache.TryGetValue(path, out value)) + if (_cache.TryGetValue(path, out string value)) { - var cacheEntryOptions = new MemoryCacheEntryOptions(); - cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(resolvedPath)); - var fileInfo = _fileProvider.GetFileInfo(resolvedPath); - - if (!fileInfo.Exists && - _requestPathBase.HasValue && - resolvedPath.StartsWith(_requestPathBase.Value, StringComparison.OrdinalIgnoreCase)) - { - var requestPathBaseRelativePath = resolvedPath.Substring(_requestPathBase.Value.Length); - cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(requestPathBaseRelativePath)); - fileInfo = _fileProvider.GetFileInfo(requestPathBaseRelativePath); - } - - if (fileInfo.Exists) - { - value = QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo)); - } - else - { - // if the file is not in the current server. - value = path; - } - - value = _cache.Set(path, value, cacheEntryOptions); + return value; } + var cacheEntryOptions = new MemoryCacheEntryOptions(); + cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(resolvedPath)); + var fileInfo = _fileProvider.GetFileInfo(resolvedPath); + + if (!fileInfo.Exists && + _requestPathBase.HasValue && + resolvedPath.StartsWith(_requestPathBase.Value, StringComparison.OrdinalIgnoreCase)) + { + var requestPathBaseRelativePath = resolvedPath.Substring(_requestPathBase.Value.Length); + cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(requestPathBaseRelativePath)); + fileInfo = _fileProvider.GetFileInfo(requestPathBaseRelativePath); + } + + if (fileInfo.Exists) + { + value = QueryHelpers.AddQueryString(path, VersionKey, GetHashForFile(fileInfo)); + } + else + { + // if the file is not in the current server. + value = path; + } + + cacheEntryOptions.SetSize(value.Length * sizeof(char)); + value = _cache.Set(path, value, cacheEntryOptions); return value; } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs index 8464d0bc90..9b756848a1 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs @@ -113,8 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal } var cacheKey = new GlobbingUrlKey(include, exclude); - List files; - if (Cache.TryGetValue(cacheKey, out files)) + if (Cache.TryGetValue(cacheKey, out List files)) { return files; } @@ -148,16 +147,21 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal matcher.AddExcludePatterns(trimmedExcludePatterns); } + var (matchedUrls, sizeInBytes) = FindFiles(matcher); + options.SetSize(sizeInBytes); + return Cache.Set( cacheKey, - FindFiles(matcher), + matchedUrls, options); } - private List FindFiles(Matcher matcher) + private (List matchedUrls, long sizeInBytes) FindFiles(Matcher matcher) { var matches = matcher.Execute(_baseGlobbingDirectory); var matchedUrls = new List(); + var sizeInBytes = 0L; + foreach (var matchedPath in matches.Files) { // Resolve the path to site root @@ -168,10 +172,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Item doesn't already exist. Insert it. matchedUrls.Insert(~index, matchedUrl); + sizeInBytes += matchedUrl.Length * sizeof(char); } } - return matchedUrls; + return (matchedUrls, sizeInBytes); } private class PathComparer : IComparer @@ -231,9 +236,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal var result = 0; var xEnumerator = new StringTokenizer(xNoExt, PathSeparator).GetEnumerator(); var yEnumerator = new StringTokenizer(yNoExt, PathSeparator).GetEnumerator(); - StringSegment xSegment; StringSegment ySegment; - while (TryGetNextSegment(ref xEnumerator, out xSegment)) + while (TryGetNextSegment(ref xEnumerator, out var xSegment)) { if (!TryGetNextSegment(ref yEnumerator, out ySegment)) { diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs index 2d3b8b9456..2b974aebbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs @@ -8,11 +8,13 @@ using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.TagHelpers { @@ -33,7 +35,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlTargetElement("link", Attributes = AppendVersionAttributeName, TagStructure = TagStructure.WithoutEndTag)] public class LinkTagHelper : UrlResolutionTagHelper { - private static readonly string FallbackJavaScriptResourceName = typeof(LinkTagHelper).Namespace + ".compiler.resources.LinkTagHelper_FallbackJavaScript.js"; @@ -103,6 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// The . /// The . /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version.")] public LinkTagHelper( IHostingEnvironment hostingEnvironment, IMemoryCache cache, @@ -112,8 +114,32 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers : base(urlHelperFactory, htmlEncoder) { HostingEnvironment = hostingEnvironment; - Cache = cache; JavaScriptEncoder = javaScriptEncoder; + Cache = cache; + } + + /// + /// Creates a new . + /// + /// The . + /// + /// The . + /// The . + /// The . + // Decorated with ActivatorUtilitiesConstructor since we want to influence tag helper activation + // to use this constructor in the default case. + [ActivatorUtilitiesConstructor] + public LinkTagHelper( + IHostingEnvironment hostingEnvironment, + TagHelperMemoryCacheProvider cacheProvider, + HtmlEncoder htmlEncoder, + JavaScriptEncoder javaScriptEncoder, + IUrlHelperFactory urlHelperFactory) + : base(urlHelperFactory, htmlEncoder) + { + HostingEnvironment = hostingEnvironment; + JavaScriptEncoder = javaScriptEncoder; + Cache = cacheProvider.Cache; } /// @@ -205,9 +231,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(FallbackTestValueAttributeName)] public string FallbackTestValue { get; set; } - protected IHostingEnvironment HostingEnvironment { get; } + protected internal IHostingEnvironment HostingEnvironment { get; } - protected IMemoryCache Cache { get; } + protected internal IMemoryCache Cache { get; } protected JavaScriptEncoder JavaScriptEncoder { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs index 15f7323db3..d8d6053d20 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs @@ -7,11 +7,14 @@ using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Html; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc.TagHelpers { @@ -85,6 +88,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// The . /// The . /// The . + [Obsolete("This constructor is obsolete and will be removed in a future version.")] public ScriptTagHelper( IHostingEnvironment hostingEnvironment, IMemoryCache cache, @@ -98,6 +102,30 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers JavaScriptEncoder = javaScriptEncoder; } + /// + /// Creates a new . + /// + /// The . + /// The . + /// The . + /// The . + /// The . + // Decorated with ActivatorUtilitiesConstructor since we want to influence tag helper activation + // to use this constructor in the default case. + [ActivatorUtilitiesConstructor] + public ScriptTagHelper( + IHostingEnvironment hostingEnvironment, + TagHelperMemoryCacheProvider cacheProvider, + HtmlEncoder htmlEncoder, + JavaScriptEncoder javaScriptEncoder, + IUrlHelperFactory urlHelperFactory) + : base(urlHelperFactory, htmlEncoder) + { + HostingEnvironment = hostingEnvironment; + Cache = cacheProvider.Cache; + JavaScriptEncoder = javaScriptEncoder; + } + /// public override int Order => -1000; @@ -169,9 +197,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [HtmlAttributeName(FallbackTestExpressionAttributeName)] public string FallbackTestExpression { get; set; } - protected IHostingEnvironment HostingEnvironment { get; } + protected internal IHostingEnvironment HostingEnvironment { get; } - protected IMemoryCache Cache { get; } + protected internal IMemoryCache Cache { get; private set; } protected JavaScriptEncoder JavaScriptEncoder { get; } diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs index a2159f251f..f913e6a59b 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.ViewEngines; @@ -73,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ImageTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), urlHelperFactory.Object) { @@ -127,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ImageTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) { @@ -170,7 +171,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext(); - var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new HtmlTestEncoder(), MakeUrlHelperFactory()) + var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) { ViewContext = viewContext, Src = "/images/test-image.png", @@ -206,7 +207,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext(); - var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new HtmlTestEncoder(), MakeUrlHelperFactory()) + var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) { ViewContext = viewContext, Src = "/images/test-image.png", @@ -242,7 +243,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext("/bar"); - var helper = new ImageTagHelper(hostingEnvironment, MakeCache(), new HtmlTestEncoder(), MakeUrlHelperFactory()) + var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) { ViewContext = viewContext, Src = "/bar/images/image.jpg", @@ -328,8 +329,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return hostingEnvironment.Object; } - private static IMemoryCache MakeCache() => new MemoryCache(new MemoryCacheOptions()); - private static IUrlHelperFactory MakeUrlHelperFactory() { var urlHelper = new Mock(); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs new file mode 100644 index 0000000000..bc6d1238c6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs @@ -0,0 +1,87 @@ +// 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.Text.Encodings.Web; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; +using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.DependencyInjection; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Razor.Internal +{ + // Tests to verify that script, link and image tag helper use the size limited instance of MemoryCache. + public class DefaultTagHelperActivatorTest + { + private readonly TagHelperMemoryCacheProvider CacheProvider = new TagHelperMemoryCacheProvider(); + private readonly IMemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); + private readonly IHostingEnvironment HostingEnvironment = Mock.Of(); + + [Fact] + public void ScriptTagHelper_DoesNotUseMemoryCacheInstanceFromDI() + { + // Arrange + var activator = new DefaultTagHelperActivator(new TypeActivatorCache()); + var viewContext = CreateViewContext(); + + var scriptTagHelper = activator.Create(viewContext); + + Assert.Same(CacheProvider.Cache, scriptTagHelper.Cache); + Assert.Same(HostingEnvironment, scriptTagHelper.HostingEnvironment); + } + + [Fact] + public void LinkTagHelper_DoesNotUseMemoryCacheInstanceFromDI() + { + // Arrange + var activator = new DefaultTagHelperActivator(new TypeActivatorCache()); + var viewContext = CreateViewContext(); + + var linkTagHelper = activator.Create(viewContext); + + Assert.Same(CacheProvider.Cache, linkTagHelper.Cache); + Assert.Same(HostingEnvironment, linkTagHelper.HostingEnvironment); + } + + [Fact] + public void ImageTagHelper_DoesNotUseMemoryCacheInstanceFromDI() + { + // Arrange + var activator = new DefaultTagHelperActivator(new TypeActivatorCache()); + var viewContext = CreateViewContext(); + + var imageTagHelper = activator.Create(viewContext); + + Assert.Same(CacheProvider.Cache, imageTagHelper.Cache); + Assert.Same(HostingEnvironment, imageTagHelper.HostingEnvironment); + } + + private ViewContext CreateViewContext() + { + var services = new ServiceCollection() + .AddSingleton(HostingEnvironment) + .AddSingleton(MemoryCache) + .AddSingleton(CacheProvider) + .AddSingleton(HtmlEncoder.Default) + .AddSingleton(JavaScriptEncoder.Default) + .AddSingleton(Mock.Of()) + .BuildServiceProvider(); + + var viewContext = new ViewContext + { + HttpContext = new DefaultHttpContext + { + RequestServices = services, + } + }; + + return viewContext; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs index 533a38faf3..b2617519ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Text; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; @@ -267,29 +266,30 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal Assert.Equal("FromCache", result); } - [Theory] - [InlineData("/hello/world", "/hello/world", null)] - [InlineData("/testApp/hello/world", "/hello/world", "/testApp")] - public void SetsValueInCache(string filePath, string watchPath, string requestPathBase) + [Fact] + public void AddFileVersionToPath_CachesEntry() => AddFileVersionToPath("/hello/world", "/hello/world", null); + + [Fact] + public void AddFileVersionToPath_WithRequestPathBase_CachesEntry() => AddFileVersionToPath("/testApp/hello/world", "/hello/world", "/testApp"); + + private static void AddFileVersionToPath(string filePath, string watchPath, string requestPathBase) { // Arrange - var changeToken = new Mock(); + var expected = filePath + "?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk"; + var expectedSize = expected.Length * sizeof(char); + var changeToken = Mock.Of(); + var fileProvider = GetMockFileProvider(filePath, requestPathBase != null); Mock.Get(fileProvider) - .Setup(f => f.Watch(watchPath)).Returns(changeToken.Object); + .Setup(f => f.Watch(watchPath)).Returns(changeToken); - object cacheValue = null; - var value = new Mock(); - value.Setup(c => c.Value).Returns(cacheValue); - value.Setup(c => c.ExpirationTokens).Returns(new List()); + var cacheEntry = Mock.Of(c => c.ExpirationTokens == new List()); var cache = new Mock(); - cache.CallBase = true; - cache.Setup(c => c.TryGetValue(It.IsAny(), out cacheValue)) - .Returns(cacheValue != null); - cache.Setup(c => c.CreateEntry( - /*key*/ filePath)) - .Returns((object key) => value.Object) + + cache.Setup(c => c.CreateEntry(filePath)) + .Returns(cacheEntry) .Verifiable(); + var fileVersionProvider = new FileVersionProvider( fileProvider, cache.Object, @@ -299,7 +299,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal var result = fileVersionProvider.AddFileVersionToPath(filePath); // Assert - Assert.Equal(filePath + "?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); + Assert.Equal(expected, result); + Assert.Equal(expected, cacheEntry.Value); + Assert.Equal(expectedSize, cacheEntry.Size); cache.VerifyAll(); } diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs index 8493f1daa5..4bce947f96 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs @@ -411,6 +411,24 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal Assert.Collection(excludePatterns, pattern => Assert.Equal($"{prefix}**/*.min.css", pattern)); } + [Fact] + public void BuildUrlList_AddsToMemoryCache_WithSizeLimit() + { + // Arrange + var cacheEntry = Mock.Of(m => m.ExpirationTokens == new List()); + var cache = Mock.Of(m => m.CreateEntry(It.IsAny()) == cacheEntry); + + var fileProvider = MakeFileProvider(MakeDirectoryContents("site.css", "blank.css")); + var requestPathBase = PathString.Empty; + var globbingUrlBuilder = new GlobbingUrlBuilder(fileProvider, cache, requestPathBase); + + // Act + var urlList = globbingUrlBuilder.BuildUrlList("/site.css", "**/*.css", excludePattern: null); + + // Assert + Assert.Equal(38, cacheEntry.Size); + } + public class FileNode { public FileNode(string name) diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index 833da15fc4..309371a185 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; @@ -70,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), urlHelperFactory.Object) @@ -167,7 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -330,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -427,7 +428,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -473,7 +474,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -584,7 +585,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -614,7 +615,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -660,7 +661,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -723,7 +724,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -764,7 +765,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -802,7 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -860,7 +861,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -939,7 +940,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -991,7 +992,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new LinkTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -1084,8 +1085,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return hostingEnvironment.Object; } - private static IMemoryCache MakeCache() => new MemoryCache(new MemoryCacheOptions()); - private static IUrlHelperFactory MakeUrlHelperFactory() { var urlHelper = new Mock(); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index 923a76b889..eb6696292a 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Html; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; @@ -71,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), urlHelperFactory.Object) @@ -118,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -311,7 +312,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -408,7 +409,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -507,7 +508,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -536,7 +537,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -581,7 +582,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -625,7 +626,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -688,7 +689,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( hostingEnvironment, - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -726,7 +727,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -760,7 +761,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -796,7 +797,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -859,7 +860,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -907,7 +908,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var helper = new ScriptTagHelper( MakeHostingEnvironment(), - MakeCache(), + new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), new JavaScriptTestEncoder(), MakeUrlHelperFactory()) @@ -997,8 +998,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return hostingEnvironment.Object; } - private static IMemoryCache MakeCache() => new MemoryCache(new MemoryCacheOptions()); - private static IUrlHelperFactory MakeUrlHelperFactory() { var urlHelper = new Mock(); From c34830f9dea700ccca47a310756fa08a4630e0ac Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 5 Jul 2018 12:03:04 -0700 Subject: [PATCH 150/316] Use OSS package versions consistent with aspnet/benchmarks and Microsoft.AspNetcore.All 2.1.2 - update our own NuGet packages to align lower-level dependencies - add metadata to BasicApi controllers - avoids analyzer failures when building with Microsoft.AspNetCore.All e.g. in benchmark runs - especially important in `PetController` because it's associated with `[ApiContoller]` - use pooled `DbContext`s for MySql too --- .../BasicApi/Controllers/PetController.cs | 17 +++++++++++++++++ .../BasicApi/Controllers/TokenController.cs | 5 ++++- benchmarkapps/BasicApi/Startup.cs | 2 +- benchmarkapps/BasicViews/Startup.cs | 2 +- build/dependencies.props | 14 +++++++------- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/benchmarkapps/BasicApi/Controllers/PetController.cs b/benchmarkapps/BasicApi/Controllers/PetController.cs index 828b445899..8a30fb18c2 100644 --- a/benchmarkapps/BasicApi/Controllers/PetController.cs +++ b/benchmarkapps/BasicApi/Controllers/PetController.cs @@ -25,6 +25,9 @@ namespace BasicApi.Controllers public BasicApiContext DbContext { get; } [HttpGet("{id}", Name = "FindPetById")] + [ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> FindById(int id) { var pet = await DbContext.Pets @@ -42,6 +45,8 @@ namespace BasicApi.Controllers [AllowAnonymous] [HttpGet("anonymous/{id}")] + [ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> FindByIdWithoutToken(int id) { var pet = await DbContext.Pets @@ -58,6 +63,9 @@ namespace BasicApi.Controllers } [HttpGet("findByCategory/{categoryId}")] + [ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> FindByCategory(int categoryId) { var pet = await DbContext.Pets @@ -74,6 +82,9 @@ namespace BasicApi.Controllers } [HttpGet("findByStatus")] + [ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> FindByStatus(string status) { var pet = await DbContext.Pets @@ -90,6 +101,9 @@ namespace BasicApi.Controllers } [HttpGet("findByTags")] + [ProducesResponseType(typeof(Pet), StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public async Task> FindByTags(string[] tags) { var pet = await DbContext.Pets @@ -107,6 +121,9 @@ namespace BasicApi.Controllers [Authorize("pet-store-writer")] [HttpPost] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status401Unauthorized)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public async Task AddPet([FromBody] Pet pet) { DbContext.Pets.Add(pet); diff --git a/benchmarkapps/BasicApi/Controllers/TokenController.cs b/benchmarkapps/BasicApi/Controllers/TokenController.cs index 624288ddd7..803a1d208e 100644 --- a/benchmarkapps/BasicApi/Controllers/TokenController.cs +++ b/benchmarkapps/BasicApi/Controllers/TokenController.cs @@ -7,6 +7,7 @@ using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Security.Claims; using Microsoft.AspNetCore.Authentication.JwtBearer; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; using Microsoft.IdentityModel.Tokens; @@ -45,11 +46,13 @@ namespace BasicApi.Controllers } [HttpGet("/token")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status403Forbidden)] public IActionResult GetToken(string username) { if (username == null || !_identities.TryGetValue(username, out var identity)) { - return new StatusCodeResult(403); + return new StatusCodeResult(StatusCodes.Status403Forbidden); } var handler = _options.SecurityTokenValidators.OfType().First(); diff --git a/benchmarkapps/BasicApi/Startup.cs b/benchmarkapps/BasicApi/Startup.cs index 8fcc4787ae..52fa10105a 100644 --- a/benchmarkapps/BasicApi/Startup.cs +++ b/benchmarkapps/BasicApi/Startup.cs @@ -68,7 +68,7 @@ namespace BasicApi case "MYSQL": services .AddEntityFrameworkMySql() - .AddDbContext(options => options.UseMySql(connectionString)); + .AddDbContextPool(options => options.UseMySql(connectionString)); break; #endif diff --git a/benchmarkapps/BasicViews/Startup.cs b/benchmarkapps/BasicViews/Startup.cs index 30ebb341bd..8d24470a28 100644 --- a/benchmarkapps/BasicViews/Startup.cs +++ b/benchmarkapps/BasicViews/Startup.cs @@ -49,7 +49,7 @@ namespace BasicViews case "MYSQL": services .AddEntityFrameworkMySql() - .AddDbContext(options => options.UseMySql(connectionString)); + .AddDbContextPool(options => options.UseMySql(connectionString)); break; #endif diff --git a/build/dependencies.props b/build/dependencies.props index 78e6a161ca..d443b2338e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,15 +7,15 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 - 2.1.0 - 2.1.0 - 2.1.0 - 0.42.1 - 2.1.0 - 2.1.0-rc1-final + 2.1.1 + 2.1.1 + 2.1.1 + 0.43.0 + 2.1.1.1 + 2.1.1 2.2.0-preview1-34823 2.2.0-preview1-17102 2.2.0-preview1-34823 From 44f5b54f5fdabf0696708654ef64a3a318767594 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 2 Aug 2018 13:37:43 +1200 Subject: [PATCH 151/316] React to routing API review (#8194) --- build/dependencies.props | 4 ++-- .../Internal/MvcEndpointDataSource.cs | 2 +- .../Routing/ActionConstraintMatcherPolicy.cs | 2 +- .../Routing/EndpointRoutingUrlHelper.cs | 9 ++++----- .../Routing/UrlHelperFactory.cs | 3 +-- .../Internal/MvcEndpointDataSourceTests.cs | 2 +- .../Routing/ActionConstraintMatcherPolicyTest.cs | 2 +- .../Routing/EndpointRoutingUrlHelperTest.cs | 2 +- 8 files changed, 12 insertions(+), 14 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d443b2338e..9ab9fc484e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34823 2.2.0-preview1-34823 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-a-preview1-routing-api-review-p1-16821 + 2.2.0-a-preview1-routing-api-review-p1-16821 2.2.0-preview1-34823 2.2.0-preview1-34823 2.2.0-preview1-34823 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index b16b9ed018..867d5dd5ed 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index 60a1abe6b1..090ade0cf1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs index 27590902e9..6f24248950 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs @@ -3,7 +3,6 @@ using System; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.EndpointFinders; using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing @@ -16,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { private readonly ILogger _logger; private readonly LinkGenerator _linkGenerator; - private readonly IEndpointFinder _routeValuesBasedEndpointFinder; + private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// /// Initializes a new instance of the class using the specified @@ -30,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing /// The . public EndpointRoutingUrlHelper( ActionContext actionContext, - IEndpointFinder routeValuesBasedEndpointFinder, + IEndpointFinder routeValuesBasedEndpointFinder, LinkGenerator linkGenerator, ILogger logger) : base(actionContext) @@ -87,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( - new RouteValuesBasedEndpointFinderContext() + new RouteValuesAddress() { ExplicitValues = valuesDictionary, AmbientValues = AmbientValues @@ -124,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( - new RouteValuesBasedEndpointFinderContext() + new RouteValuesAddress() { RouteName = routeContext.RouteName, ExplicitValues = valuesDictionary, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index b6c5acfdf4..fb6bbb0ae5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -5,7 +5,6 @@ using System; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.EndpointFinders; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -52,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { var services = httpContext.RequestServices; var linkGenerator = services.GetRequiredService(); - var routeValuesBasedEndpointFinder = services.GetRequiredService>(); + var routeValuesBasedEndpointFinder = services.GetRequiredService>(); var logger = services.GetRequiredService>(); urlHelper = new EndpointRoutingUrlHelper( diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index c49d9f0243..6304099fc0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Moq; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 51a00d6ddd..77288f75d3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -13,7 +13,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index faa0eebf0c..867832e808 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matchers; +using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; From 2b289d2f2c353c2d87ceff3255d366adfc332e5f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 31 Jul 2018 20:58:26 -0700 Subject: [PATCH 152/316] Use MatcherPolicy for Consumes --- .../MvcCoreServiceCollectionExtensions.cs | 1 + .../Internal/MvcEndpointDataSource.cs | 6 + .../Routing/ConsumesMatcherPolicy.cs | 244 ++++++++++++++++++ .../Routing/ConsumesMetadata.cs | 23 ++ .../MvcCoreServiceCollectionExtensionsTest.cs | 1 + .../Routing/ConsumesMatcherPolicyTest.cs | 237 +++++++++++++++++ .../ConsumesAttributeEndpointRoutingTests.cs | 18 ++ .../ConsumesAttributeTestsBase.cs | 2 +- .../RequestServicesEndpointRoutingTest.cs | 1 + 9 files changed, 532 insertions(+), 1 deletion(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 477a67ac0a..3ebdc371fa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -175,6 +175,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddEnumerable(ServiceDescriptor.Transient()); // Policies for Endpoints + services.TryAddEnumerable(ServiceDescriptor.Singleton()); services.TryAddEnumerable(ServiceDescriptor.Singleton()); // diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 867d5dd5ed..180b34011f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Metadata; @@ -348,6 +349,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal { metadata.Add(new HttpMethodMetadata(httpMethodActionConstraint.HttpMethods)); } + else if (actionConstraint is ConsumesAttribute consumesAttribute && + !metadata.OfType().Any()) + { + metadata.Add(new ConsumesMetadata(consumesAttribute.ContentTypes.ToArray())); + } else if (!metadata.Contains(actionConstraint)) { // The constraint might have been added earlier, e.g. it is also a filter descriptor diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs new file mode 100644 index 0000000000..dcaeaca936 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -0,0 +1,244 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + internal class ConsumesMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy + { + internal const string Http415EndpointDisplayName = "415 HTTP Unsupported Media Type"; + internal const string AnyContentType = "*/*"; + + // Run after HTTP methods, but before 'default'. + public override int Order { get; } = -100; + + public IComparer Comparer { get; } = new ConsumesMetadataEndpointComparer(); + + public bool AppliesToNode(IReadOnlyList endpoints) + { + if (endpoints == null) + { + throw new ArgumentNullException(nameof(endpoints)); + } + + return endpoints.Any(e => e.Metadata.GetMetadata()?.ContentTypes.Count > 0); + } + + public IReadOnlyList GetEdges(IReadOnlyList endpoints) + { + if (endpoints == null) + { + throw new ArgumentNullException(nameof(endpoints)); + } + + // The algorithm here is designed to be preserve the order of the endpoints + // while also being relatively simple. Preserving order is important. + + // First, build a dictionary of all of the content-type patterns that are included + // at this node. + // + // For now we're just building up the set of keys. We don't add any endpoints + // to lists now because we don't want ordering problems. + var edges = new Dictionary>(StringComparer.OrdinalIgnoreCase); + for (var i = 0; i < endpoints.Count; i++) + { + var endpoint = endpoints[i]; + var contentTypes = endpoint.Metadata.GetMetadata()?.ContentTypes; + if (contentTypes == null || contentTypes.Count == 0) + { + contentTypes = new string[] { AnyContentType, }; + } + + for (var j = 0; j < contentTypes.Count; j++) + { + var contentType = contentTypes[j]; + + if (!edges.ContainsKey(contentType)) + { + edges.Add(contentType, new List()); + } + } + } + + // Now in a second loop, add endpoints to these lists. We've enumerated all of + // the states, so we want to see which states this endpoint matches. + for (var i = 0; i < endpoints.Count; i++) + { + var endpoint = endpoints[i]; + var contentTypes = endpoint.Metadata.GetMetadata()?.ContentTypes ?? Array.Empty(); + if (contentTypes.Count == 0) + { + // OK this means that this endpoint matches *all* content methods. + // So, loop and add it to all states. + foreach (var kvp in edges) + { + kvp.Value.Add(endpoint); + } + } + else + { + // OK this endpoint matches specific content types -- we have to loop through edges here + // because content types could either be exact (like 'application/json') or they + // could have wildcards (like 'text/*'). We don't expect wildcards to be especially common + // with consumes, but we need to support it. + foreach (var kvp in edges) + { + // The edgeKey maps to a possible request header value + var edgeKey = new MediaType(kvp.Key); + + for (var j = 0; j < contentTypes.Count; j++) + { + var contentType = contentTypes[j]; + + var mediaType = new MediaType(contentType); + + // Example: 'application/json' is subset of 'application/*' + // + // This means that when the request has content-type 'application/json' an endpoint + // what consumes 'application/*' should match. + if (edgeKey.IsSubsetOf(mediaType)) + { + kvp.Value.Add(endpoint); + + // It's possible that a ConsumesMetadata defines overlapping wildcards. Don't add an endpoint + // to any edge twice + break; + } + } + } + } + } + + // If after we're done there isn't any endpoint that accepts */*, then we'll synthesize an + // endpoint that always returns a 415. + if (!edges.ContainsKey(AnyContentType)) + { + edges.Add(AnyContentType, new List() + { + CreateRejectionEndpoint(), + }); + } + + return edges + .Select(kvp => new PolicyNodeEdge(kvp.Key, kvp.Value)) + .ToArray(); + } + + private Endpoint CreateRejectionEndpoint() + { + return new MatcherEndpoint( + (next) => (context) => + { + context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return Task.CompletedTask; + }, + RoutePatternFactory.Parse("/"), + new RouteValueDictionary(), + 0, + EndpointMetadataCollection.Empty, + Http415EndpointDisplayName); + } + + public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList edges) + { + if (edges == null) + { + throw new ArgumentNullException(nameof(edges)); + } + + // Since our 'edges' can have wildcards, we do a sort based on how wildcard-ey they + // are then then execute them in linear order. + var ordered = edges + .Select(e => (mediaType: new MediaType((string)e.State), destination: e.Destination)) + .OrderBy(e => GetScore(e.mediaType)) + .ToArray(); + + // If any edge matches all content types, then treat that as the 'exit'. This will + // always happen because we insert a 415 endpoint. + for (var i = 0; i < ordered.Length; i++) + { + if (ordered[i].mediaType.MatchesAllTypes) + { + exitDestination = ordered[i].destination; + break; + } + } + + return new ConsumesPolicyJumpTable(exitDestination, ordered); + } + + private int GetScore(MediaType mediaType) + { + // Higher score == lower priority - see comments on MediaType. + if (mediaType.MatchesAllTypes) + { + return 4; + } + else if (mediaType.MatchesAllSubTypes) + { + return 3; + } + else if (mediaType.MatchesAllSubTypesWithoutSuffix) + { + return 2; + } + else + { + return 1; + } + } + + private class ConsumesMetadataEndpointComparer : EndpointMetadataComparer + { + protected override int CompareMetadata(IConsumesMetadata x, IConsumesMetadata y) + { + // Ignore the metadata if it has an empty list of content types. + return base.CompareMetadata( + x?.ContentTypes.Count > 0 ? x : null, + y?.ContentTypes.Count > 0 ? y : null); + } + } + + private class ConsumesPolicyJumpTable : PolicyJumpTable + { + private (MediaType mediaType, int destination)[] _destinations; + private int _exitDestination; + + public ConsumesPolicyJumpTable(int exitDestination, (MediaType mediaType, int destination)[] destinations) + { + _exitDestination = exitDestination; + _destinations = destinations; + } + + public override int GetDestination(HttpContext httpContext) + { + var contentType = httpContext.Request.ContentType; + if (string.IsNullOrEmpty(contentType)) + { + return _exitDestination; + } + + var requestMediaType = new MediaType(contentType); + var destinations = _destinations; + for (var i = 0; i < destinations.Length; i++) + { + if (requestMediaType.IsSubsetOf(destinations[i].mediaType)) + { + return destinations[i].destination; + } + } + + return _exitDestination; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs new file mode 100644 index 0000000000..40c7c86f1d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs @@ -0,0 +1,23 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + internal class ConsumesMetadata : IConsumesMetadata + { + public ConsumesMetadata(string[] contentTypes) + { + if (contentTypes == null) + { + throw new ArgumentNullException(nameof(contentTypes)); + } + + ContentTypes = contentTypes; + } + + public IReadOnlyList ContentTypes { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index 9660812ec5..f7223852de 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -325,6 +325,7 @@ namespace Microsoft.AspNetCore.Mvc typeof(MatcherPolicy), new Type[] { + typeof(ConsumesMatcherPolicy), typeof(ActionConstraintMatcherPolicy), } }, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs new file mode 100644 index 0000000000..d52ddebf74 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs @@ -0,0 +1,237 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.AspNetCore.Routing.Patterns; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Routing +{ + public class ConsumesMatcherPolicyTest + { + [Fact] + public void AppliesToNode_EndpointWithoutMetadata_ReturnsFalse() + { + // Arrange + var endpoints = new[] { CreateEndpoint("/", null), }; + + var policy = CreatePolicy(); + + // Act + var result = policy.AppliesToNode(endpoints); + + // Assert + Assert.False(result); + } + + [Fact] + public void AppliesToNode_EndpointWithoutContentTypes_ReturnsFalse() + { + // Arrange + var endpoints = new[] + { + CreateEndpoint("/", new ConsumesMetadata(Array.Empty())), + }; + + var policy = CreatePolicy(); + + // Act + var result = policy.AppliesToNode(endpoints); + + // Assert + Assert.False(result); + } + + [Fact] + public void AppliesToNode_EndpointHasContentTypes_ReturnsTrue() + { + // Arrange + var endpoints = new[] + { + CreateEndpoint("/", new ConsumesMetadata(Array.Empty())), + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", })), + }; + + var policy = CreatePolicy(); + + // Act + var result = policy.AppliesToNode(endpoints); + + // Assert + Assert.True(result); + } + + [Fact] + public void GetEdges_GroupsByContentType() + { + // Arrange + var endpoints = new[] + { + // These are arrange in an order that we won't actually see in a product scenario. It's done + // this way so we can verify that ordering is preserved by GetEdges. + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", "application/*+json", })), + CreateEndpoint("/", new ConsumesMetadata(Array.Empty())), + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/xml", "application/*+xml", })), + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/*", })), + CreateEndpoint("/", new ConsumesMetadata(new[]{ "*/*", })), + }; + + var policy = CreatePolicy(); + + // Act + var edges = policy.GetEdges(endpoints); + + // Assert + Assert.Collection( + edges.OrderBy(e => e.State), + e => + { + Assert.Equal("*/*", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[4], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/*", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/*+json", e.State); + Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/*+xml", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/json", e.State); + Assert.Equal(new[] { endpoints[0], endpoints[1], endpoints[3], endpoints[4], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/xml", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[2], endpoints[3], endpoints[4], }, e.Endpoints.ToArray()); + }); + } + + [Fact] // See explanation in GetEdges for how this case is different + public void GetEdges_GroupsByContentType_CreatesHttp405Endpoint() + { + // Arrange + var endpoints = new[] + { + // These are arrange in an order that we won't actually see in a product scenario. It's done + // this way so we can verify that ordering is preserved by GetEdges. + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/json", "application/*+json", })), + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/xml", "application/*+xml", })), + CreateEndpoint("/", new ConsumesMetadata(new[] { "application/*", })), + }; + + var policy = CreatePolicy(); + + // Act + var edges = policy.GetEdges(endpoints); + + // Assert + Assert.Collection( + edges.OrderBy(e => e.State), + e => + { + Assert.Equal("*/*", e.State); + Assert.Equal(ConsumesMatcherPolicy.Http415EndpointDisplayName, Assert.Single(e.Endpoints).DisplayName); + }, + e => + { + Assert.Equal("application/*", e.State); + Assert.Equal(new[] { endpoints[2], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/*+json", e.State); + Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/*+xml", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/json", e.State); + Assert.Equal(new[] { endpoints[0], endpoints[2], }, e.Endpoints.ToArray()); + }, + e => + { + Assert.Equal("application/xml", e.State); + Assert.Equal(new[] { endpoints[1], endpoints[2], }, e.Endpoints.ToArray()); + }); + + } + + [Theory] + [InlineData("image/png", 1)] + [InlineData("application/foo", 2)] + [InlineData("text/xml", 3)] + [InlineData("application/product+json", 6)] // application/json will match this + [InlineData("application/product+xml", 7)] // application/xml will match this + [InlineData("application/json", 6)] + [InlineData("application/xml", 7)] + public void BuildJumpTable_SortsEdgesByPriority(string contentType, int expected) + { + // Arrange + var edges = new PolicyJumpTableEdge[] + { + // In reverse order of how they should be processed + new PolicyJumpTableEdge("*/*", 1), + new PolicyJumpTableEdge("application/*", 2), + new PolicyJumpTableEdge("text/*", 3), + new PolicyJumpTableEdge("application/*+xml", 4), + new PolicyJumpTableEdge("application/*+json", 5), + new PolicyJumpTableEdge("application/json", 6), + new PolicyJumpTableEdge("application/xml", 7), + }; + + var policy = CreatePolicy(); + + var jumpTable = policy.BuildJumpTable(-1, edges); + + var httpContext = new DefaultHttpContext(); + httpContext.Request.ContentType = contentType; + + // Act + var actual = jumpTable.GetDestination(httpContext); + + // Assert + Assert.Equal(expected, actual); + } + + private static MatcherEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata) + { + var metadata = new List(); + if (consumesMetadata != null) + { + metadata.Add(consumesMetadata); + } + + return new MatcherEndpoint( + (next) => null, + RoutePatternFactory.Parse(template), + new RouteValueDictionary(), + 0, + new EndpointMetadataCollection(metadata), + $"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty())}"); + } + + private static ConsumesMatcherPolicy CreatePolicy() + { + return new ConsumesMatcherPolicy(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs index 7377442ac6..9aebbcb2d4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -29,5 +30,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.True(result); } + + // The endpoint routing version of this feature has fixed https://github.com/aspnet/Mvc/issues/8174 + [Fact] + public override async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() + { + // Arrange + var request = new HttpRequestMessage( + HttpMethod.Post, + "http://localhost/ConsumesAttribute_PassThrough/CreateProduct"); + + // Act + var response = await Client.SendAsync(request); + var body = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index acc68febbc..302d50fc9d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() + public virtual async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() { // Arrange var request = new HttpRequestMessage( diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs index 91cba5aeb5..113cfde4dc 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; From b7335ac768024187fb1aafaa033096eb04ec6cec Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 10 Jul 2018 10:17:42 -0700 Subject: [PATCH 153/316] Add a code fix that applies ProducesResponseTypeAttributes --- .../AddResponseTypeAttributeCodeFixAction.cs | 152 ++++++++++++++++++ ...AddResponseTypeAttributeCodeFixProvider.cs | 40 +++++ .../ApiControllerSymbolCache.cs | 3 + .../ApiConventionAnalyzer.cs | 68 ++------ .../DeclaredApiResponseMetadata.cs | 75 ++++++++- .../SymbolApiResponseMetadataProvider.cs | 73 ++++++--- .../SymbolNames.cs | 2 + ...AttributeCodeFixProviderIntegrationTest.cs | 70 ++++++++ .../SymbolApiResponseMetadataProviderTest.cs | 64 ++++---- ...ullyQualifiedProducesResponseType.Input.cs | 35 ++++ ...llyQualifiedProducesResponseType.Output.cs | 39 +++++ .../CodeFixAddsMissingStatusCodes.Input.cs | 23 +++ .../CodeFixAddsMissingStatusCodes.Output.cs | 26 +++ .../CodeFixAddsStatusCodes.Input.cs | 17 ++ .../CodeFixAddsStatusCodes.Output.cs | 20 +++ .../CodeFixAddsSuccessStatusCode.Input.cs | 26 +++ .../CodeFixAddsSuccessStatusCode.Output.cs | 30 ++++ ...hConventionAddsMissingStatusCodes.Input.cs | 21 +++ ...ConventionAddsMissingStatusCodes.Output.cs | 24 +++ ...ntionMethodAddsMissingStatusCodes.Input.cs | 18 +++ ...tionMethodAddsMissingStatusCodes.Output.cs | 20 +++ ...onvention_ReturnsUndocumentedStatusCode.cs | 10 +- 22 files changed, 752 insertions(+), 104 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs create mode 100644 test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs new file mode 100644 index 0000000000..56f2373203 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs @@ -0,0 +1,152 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeActions; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Editing; +using Microsoft.CodeAnalysis.Simplification; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + internal sealed class AddResponseTypeAttributeCodeFixAction : CodeAction + { + private readonly Document _document; + private readonly Diagnostic _diagnostic; + + public AddResponseTypeAttributeCodeFixAction(Document document, Diagnostic diagnostic) + { + _document = document; + _diagnostic = diagnostic; + } + + public override string Title => "Add ProducesResponseType attributes."; + + protected override async Task GetChangedDocumentAsync(CancellationToken cancellationToken) + { + var context = await CreateCodeActionContext(cancellationToken).ConfigureAwait(false); + var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(context.SymbolCache, context.Method); + + var statusCodes = CalculateStatusCodesToApply(context, declaredResponseMetadata); + if (statusCodes.Count == 0) + { + return _document; + } + + var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); + foreach (var statusCode in statusCodes.OrderBy(s => s)) + { + documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(statusCode)); + } + + if (!declaredResponseMetadata.Any(m => m.IsDefault && m.AttributeSource == context.Method)) + { + // Add a ProducesDefaultResponseTypeAttribute if the method does not already have one. + documentEditor.AddAttribute(context.MethodSyntax, CreateProducesDefaultResponseTypeAttribute()); + } + + var apiConventionMethodAttribute = context.Method.GetAttributes(context.SymbolCache.ApiConventionMethodAttribute).FirstOrDefault(); + + if (apiConventionMethodAttribute != null) + { + // Remove [ApiConventionMethodAttribute] declared on the method since it's no longer required + var attributeSyntax = await apiConventionMethodAttribute + .ApplicationSyntaxReference + .GetSyntaxAsync(cancellationToken) + .ConfigureAwait(false); + + documentEditor.RemoveNode(attributeSyntax); + } + + return documentEditor.GetChangedDocument(); + } + + private async Task CreateCodeActionContext(CancellationToken cancellationToken) + { + var root = await _document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + var semanticModel = await _document.GetSemanticModelAsync(cancellationToken).ConfigureAwait(false); + var methodReturnStatement = (ReturnStatementSyntax)root.FindNode(_diagnostic.Location.SourceSpan); + var methodSyntax = methodReturnStatement.FirstAncestorOrSelf(); + var method = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken); + + var symbolCache = new ApiControllerSymbolCache(semanticModel.Compilation); + + var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, cancellationToken); + return codeActionContext; + } + + private ICollection CalculateStatusCodesToApply(CodeActionContext context, IList declaredResponseMetadata) + { + if (!SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata)) + { + // If we cannot parse metadata correctly, don't offer fixes. + return Array.Empty(); + } + + var statusCodes = new HashSet(); + foreach (var metadata in actualResponseMetadata) + { + if (DeclaredApiResponseMetadata.TryGetDeclaredMetadata(declaredResponseMetadata, metadata, result: out var declaredMetadata) && + declaredMetadata.AttributeSource == context.Method) + { + // A ProducesResponseType attribute is declared on the method for the current status code. + continue; + } + + statusCodes.Add(metadata.IsDefaultResponse ? 200 : metadata.StatusCode); + } + + return statusCodes; + } + + private static AttributeSyntax CreateProducesResponseTypeAttribute(int statusCode) + { + return SyntaxFactory.Attribute( + SyntaxFactory.ParseName(SymbolNames.ProducesResponseTypeAttribute) + .WithAdditionalAnnotations(Simplifier.Annotation), + SyntaxFactory.AttributeArgumentList().AddArguments( + SyntaxFactory.AttributeArgument( + SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode))))); + } + + private static AttributeSyntax CreateProducesDefaultResponseTypeAttribute() + { + return SyntaxFactory.Attribute( + SyntaxFactory.ParseName(SymbolNames.ProducesDefaultResponseTypeAttribute) + .WithAdditionalAnnotations(Simplifier.Annotation)); + } + + private readonly struct CodeActionContext + { + public CodeActionContext( + SemanticModel semanticModel, + ApiControllerSymbolCache symbolCache, + IMethodSymbol method, + MethodDeclarationSyntax methodSyntax, + CancellationToken cancellationToken) + { + SemanticModel = semanticModel; + SymbolCache = symbolCache; + Method = method; + MethodSyntax = methodSyntax; + CancellationToken = cancellationToken; + } + + public MethodDeclarationSyntax MethodSyntax { get; } + + public IMethodSymbol Method { get; } + + public SemanticModel SemanticModel { get; } + + public ApiControllerSymbolCache SymbolCache { get; } + + public CancellationToken CancellationToken { get; } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs new file mode 100644 index 0000000000..fc69730061 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs @@ -0,0 +1,40 @@ +// 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.Immutable; +using System.Composition; +using System.Threading.Tasks; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CodeFixes; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [ExportCodeFixProvider(LanguageNames.CSharp)] + [Shared] + public class AddResponseTypeAttributeCodeFixProvider : CodeFixProvider + { + public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( + DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id, + DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id); + + public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) + { + if (context.Diagnostics.Length == 0) + { + return Task.CompletedTask; + } + + var diagnostic = context.Diagnostics[0]; + if ((diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id) && + (diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id)) + { + return Task.CompletedTask; + } + + var codeFix = new AddResponseTypeAttributeCodeFixAction(context.Document, diagnostic); + + context.RegisterCodeFix(codeFix, diagnostic); + return Task.CompletedTask; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs index a28cee2458..56097763d5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs @@ -23,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers ModelStateDictionary = compilation.GetTypeByMetadataName(SymbolNames.ModelStateDictionary); NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); + ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesDefaultResponseTypeAttribute); ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); @@ -58,6 +59,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public INamedTypeSymbol NonControllerAttribute { get; } + public INamedTypeSymbol ProducesDefaultResponseTypeAttribute { get; } + public INamedTypeSymbol ProducesResponseTypeAttribute { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs index 30b4b507d8..39d810f42e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Collections.Immutable; -using System.Linq; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -51,34 +50,32 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return; } - var conventionAttributes = GetConventionTypeAttributes(symbolCache, method); - var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, conventionAttributes); + var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); + var hasUnreadableStatusCodes = !SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata); - var hasUnreadableStatusCodes = SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata); var hasUndocumentedStatusCodes = false; - foreach (var item in actualResponseMetadata) + foreach (var actualMetadata in actualResponseMetadata) { - var location = item.ReturnStatement.GetLocation(); + var location = actualMetadata.ReturnStatement.GetLocation(); - if (item.IsDefaultResponse) + if (!DeclaredApiResponseMetadata.Contains(declaredResponseMetadata, actualMetadata)) { - if (!(HasStatusCode(declaredResponseMetadata, 200) || HasStatusCode(declaredResponseMetadata, 201))) + hasUndocumentedStatusCodes = true; + if (actualMetadata.IsDefaultResponse) { - hasUndocumentedStatusCodes = true; syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, location)); } - } - else if (!HasStatusCode(declaredResponseMetadata, item.StatusCode)) + else { - hasUndocumentedStatusCodes = true; syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, location, - item.StatusCode)); + actualMetadata.StatusCode)); } } + } if (hasUndocumentedStatusCodes || hasUnreadableStatusCodes) { @@ -89,59 +86,24 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers for (var i = 0; i < declaredResponseMetadata.Count; i++) { - var expectedStatusCode = declaredResponseMetadata[i].StatusCode; - if (!HasStatusCode(actualResponseMetadata, expectedStatusCode)) + var declaredMetadata = declaredResponseMetadata[i]; + if (!Contains(actualResponseMetadata, declaredMetadata)) { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, methodSyntax.Identifier.GetLocation(), - expectedStatusCode)); + declaredMetadata.StatusCode)); } } }, SyntaxKind.MethodDeclaration); } - internal IReadOnlyList GetConventionTypeAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol method) - { - var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); - if (attributes.Length == 0) - { - attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); - } - - return attributes; - } - - internal static bool HasStatusCode(IList declaredApiResponseMetadata, int statusCode) - { - if (declaredApiResponseMetadata.Count == 0) - { - // When no status code is declared, a 200 OK is implied. - return statusCode == 200; - } - - for (var i = 0; i < declaredApiResponseMetadata.Count; i++) - { - if (declaredApiResponseMetadata[i].StatusCode == statusCode) - { - return true; - } - } - - return false; - } - - internal static bool HasStatusCode(IList actualResponseMetadata, int statusCode) + internal static bool Contains(IList actualResponseMetadata, DeclaredApiResponseMetadata declaredMetadata) { for (var i = 0; i < actualResponseMetadata.Count; i++) { - if (actualResponseMetadata[i].IsDefaultResponse) - { - return statusCode == 200 || statusCode == 201; - } - - else if(actualResponseMetadata[i].StatusCode == statusCode) + if (declaredMetadata.Matches(actualResponseMetadata[i])) { return true; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs index 75aa335209..e2cbd6c994 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs @@ -1,23 +1,92 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Analyzers { internal readonly struct DeclaredApiResponseMetadata { - public DeclaredApiResponseMetadata(int statusCode, AttributeData attributeData, IMethodSymbol convention) + public static DeclaredApiResponseMetadata ImplicitResponse { get; } = + new DeclaredApiResponseMetadata(statusCode: 0, attributeData: null, attributeSource: null, @implicit: true, @default: false); + + public static DeclaredApiResponseMetadata ForProducesResponseType(int statusCode, AttributeData attributeData, IMethodSymbol attributeSource) + { + return new DeclaredApiResponseMetadata(statusCode, attributeData, attributeSource, @implicit: false, @default: false); + } + + public static DeclaredApiResponseMetadata ForProducesDefaultResponse(AttributeData attributeData, IMethodSymbol attributeSource) + { + return new DeclaredApiResponseMetadata(statusCode: 0, attributeData, attributeSource, @implicit: false, @default: true); + } + + private DeclaredApiResponseMetadata( + int statusCode, + AttributeData attributeData, + IMethodSymbol attributeSource, + bool @implicit, + bool @default) { StatusCode = statusCode; Attribute = attributeData; - Convention = convention; + AttributeSource = attributeSource; + IsImplicit = @implicit; + IsDefault = @default; } public int StatusCode { get; } public AttributeData Attribute { get; } - public IMethodSymbol Convention { get; } + public IMethodSymbol AttributeSource { get; } + + public bool IsImplicit { get; } + + public bool IsDefault { get; } + + internal static bool Contains(IList declaredApiResponseMetadata, ActualApiResponseMetadata actualMetadata) + { + return TryGetDeclaredMetadata(declaredApiResponseMetadata, actualMetadata, out _); + } + + internal static bool TryGetDeclaredMetadata( + IList declaredApiResponseMetadata, + ActualApiResponseMetadata actualMetadata, + out DeclaredApiResponseMetadata result) + { + for (var i = 0; i < declaredApiResponseMetadata.Count; i++) + { + var declaredMetadata = declaredApiResponseMetadata[i]; + + if (declaredMetadata.Matches(actualMetadata)) + { + result = declaredMetadata; + return true; + } + } + + result = default; + return false; + } + + internal bool Matches(ActualApiResponseMetadata actualMetadata) + { + if (actualMetadata.IsDefaultResponse) + { + return IsImplicit || StatusCode == 200 || StatusCode == 201; + } + else if (actualMetadata.StatusCode == StatusCode) + { + return true; + } + else if (actualMetadata.StatusCode >= 400 && IsDefault) + { + // ProducesDefaultResponse matches any failure code + return true; + } + + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs index 9f51139f23..545955782f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -16,11 +16,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private const string StatusCodeProperty = "StatusCode"; private const string StatusCodeConstructorParameter = "statusCode"; private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; + private static readonly IList DefaultResponseMetadatas = new[] + { + DeclaredApiResponseMetadata.ImplicitResponse, + }; internal static IList GetDeclaredResponseMetadata( ApiControllerSymbolCache symbolCache, - IMethodSymbol method, - IReadOnlyList conventionTypeAttributes) + IMethodSymbol method) { var metadataItems = GetResponseMetadataFromMethodAttributes(symbolCache, method); if (metadataItems.Count != 0) @@ -28,19 +31,28 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return metadataItems; } + var conventionTypeAttributes = GetConventionTypes(symbolCache, method); metadataItems = GetResponseMetadataFromConventions(symbolCache, method, conventionTypeAttributes); + + if (metadataItems.Count == 0) + { + // If no metadata can be gleaned either through explicit attributes on the method or via a convention, + // declare an implicit 200 status code. + metadataItems = DefaultResponseMetadatas; + } + return metadataItems; } private static IList GetResponseMetadataFromConventions( ApiControllerSymbolCache symbolCache, IMethodSymbol method, - IReadOnlyList attributes) + IReadOnlyList conventionTypes) { var conventionMethod = GetMethodFromConventionMethodAttribute(symbolCache, method); if (conventionMethod == null) { - conventionMethod = MatchConventionMethod(symbolCache, method, attributes); + conventionMethod = MatchConventionMethod(symbolCache, method, conventionTypes); } if (conventionMethod != null) @@ -49,8 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } return Array.Empty(); - } - + } private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method) { var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true) @@ -87,17 +98,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private static IMethodSymbol MatchConventionMethod( ApiControllerSymbolCache symbolCache, IMethodSymbol method, - IReadOnlyList attributes) + IReadOnlyList conventionTypes) { - foreach (var attribute in attributes) + foreach (var conventionType in conventionTypes) { - if (attribute.ConstructorArguments.Length != 1 || - attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type || - !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType)) - { - continue; - } - foreach (var conventionMethod in conventionType.GetMembers().OfType()) { if (!conventionMethod.IsStatic || conventionMethod.DeclaredAccessibility != Accessibility.Public) @@ -122,14 +126,45 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers foreach (var attribute in responseMetadataAttributes) { var statusCode = GetStatusCode(attribute); - var metadata = new DeclaredApiResponseMetadata(statusCode, attribute, convention: null); + var metadata = DeclaredApiResponseMetadata.ForProducesResponseType(statusCode, attribute, attributeSource: methodSymbol); metadataItems.Add(metadata); } + var producesDefaultResponse = methodSymbol.GetAttributes(symbolCache.ProducesDefaultResponseTypeAttribute, inherit: true).FirstOrDefault(); + if (producesDefaultResponse != null) + { + metadataItems.Add(DeclaredApiResponseMetadata.ForProducesDefaultResponse(producesDefaultResponse, methodSymbol)); + } + return metadataItems; } + internal static IReadOnlyList GetConventionTypes(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + { + var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); + if (attributes.Length == 0) + { + attributes = method.ContainingAssembly.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); + } + + var conventionTypes = new List(); + for (var i = 0; i < attributes.Length; i++) + { + var attribute = attributes[i]; + if (attribute.ConstructorArguments.Length != 1 || + attribute.ConstructorArguments[0].Kind != TypedConstantKind.Type || + !(attribute.ConstructorArguments[0].Value is ITypeSymbol conventionType)) + { + continue; + } + + conventionTypes.Add(conventionType); + } + + return conventionTypes; + } + internal static int GetStatusCode(AttributeData attribute) { const int DefaultStatusCode = 200; @@ -183,7 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { actualResponseMetadata = new List(); - var hasUnreadableReturnStatements = false; + var allReturnStatementsReadable = true; foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) { @@ -199,11 +234,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } else { - hasUnreadableReturnStatements = true; + allReturnStatementsReadable = false; } } - return hasUnreadableReturnStatements; + return allReturnStatementsReadable; } internal static ActualApiResponseMetadata? InspectReturnStatementSyntax( diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index db0874e74e..ca00478262 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string PartialMethod = "Partial"; + public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute"; + public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; public const string RenderPartialMethod = "RenderPartial"; diff --git a/test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs new file mode 100644 index 0000000000..cfcf6f63f7 --- /dev/null +++ b/test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs @@ -0,0 +1,70 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class AddResponseTypeAttributeCodeFixProviderIntegrationTest + { + private MvcDiagnosticAnalyzerRunner AnalyzerRunner { get; } = new MvcDiagnosticAnalyzerRunner(new ApiConventionAnalyzer()); + + private CodeFixRunner CodeFixRunner => CodeFixRunner.Default; + + [Fact] + public Task CodeFixAddsStatusCodes() => RunTest(); + + [Fact] + public Task CodeFixAddsMissingStatusCodes() => RunTest(); + + [Fact] + public Task CodeFixWithConventionAddsMissingStatusCodes() => RunTest(); + + [Fact] + public Task CodeFixWithConventionMethodAddsMissingStatusCodes() => RunTest(); + + [Fact] + public Task CodeFixAddsSuccessStatusCode() => RunTest(); + + [Fact] + public Task CodeFixAddsFullyQualifiedProducesResponseType() => RunTest(); + + private async Task RunTest([CallerMemberName] string testMethod = "") + { + // Arrange + var project = GetProject(testMethod); + var controllerDocument = project.DocumentIds[0]; + + var expectedOutput = Read(testMethod + ".Output"); + + // Act + var diagnostics = await AnalyzerRunner.GetDiagnosticsAsync(project); + var actualOutput = await CodeFixRunner.ApplyCodeFixAsync( + new AddResponseTypeAttributeCodeFixProvider(), + project.GetDocument(controllerDocument), + diagnostics[0]); + + Assert.Equal(expectedOutput, actualOutput, ignoreLineEndingDifferences: true); + } + + private Project GetProject(string testMethod) + { + var testSource = Read(testMethod + ".Input"); + return DiagnosticProject.Create(GetType().Assembly, new[] { testSource }); + } + + private string Read(string fileName) + { + return MvcTestSource.Read(GetType().Name, fileName) + .Source + .Replace("_INPUT_", "_TEST_") + .Replace("_OUTPUT_", "_TEST_"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index ace7293af7..e31f22e8ce 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; @@ -29,10 +27,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert - Assert.Empty(result); + Assert.Collection( + result, + metadata => Assert.True(metadata.IsImplicit)); } [Fact] @@ -45,10 +45,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert - Assert.Empty(result); + Assert.Collection( + result, + metadata => Assert.True(metadata.IsImplicit)); } [Fact] @@ -61,10 +63,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert - Assert.Empty(result); + Assert.Collection( + result, + metadata => Assert.True(metadata.IsImplicit)); } [Fact] @@ -77,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -86,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(201, metadata.StatusCode); Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); + Assert.Equal(method, metadata.AttributeSource); }); } @@ -100,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -109,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(202, metadata.StatusCode); Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); + Assert.Equal(method, metadata.AttributeSource); }); } @@ -123,7 +127,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -132,7 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(203, metadata.StatusCode); Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); + Assert.Equal(method, metadata.AttributeSource); }); } @@ -146,7 +150,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -155,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(201, metadata.StatusCode); Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); + Assert.Equal(method, metadata.AttributeSource); }); } @@ -169,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -178,7 +182,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(201, metadata.StatusCode); Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); }); } @@ -192,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -206,6 +209,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { Assert.Equal(404, metadata.StatusCode); Assert.NotNull(metadata.Attribute); + }, + metadata => + { + Assert.True(metadata.IsDefault); }); } @@ -219,7 +226,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -241,16 +248,18 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert - Assert.Empty(result); + Assert.Collection( + result, + metadata => Assert.True(metadata.IsImplicit)); } [Fact] public Task GetResponseMetadata_IgnoresAttributesWithIncorrectStatusCodeType() { - return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues( + return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues( nameof(GetResponseMetadata_ControllerActionWithAttributes), nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseTypeWithIncorrectStatusCodeType)); } @@ -258,12 +267,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [Fact] public Task GetResponseMetadata_IgnoresDerivedAttributesWithoutPropertyOnConstructorArguments() { - return GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues( + return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues( nameof(GetResponseMetadata_ControllerActionWithAttributes), nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithoutArguments)); } - private async Task GetResponseMetadata_IgnoresInvalidOrUnsupportedAttribues(string typeName, string methodName) + private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues(string typeName, string methodName) { // Arrange var compilation = await GetResponseMetadataCompilation(); @@ -272,7 +281,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var symbolCache = new ApiControllerSymbolCache(compilation); // Act - var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method, Array.Empty()); + var result = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); // Assert Assert.Collection( @@ -280,8 +289,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers metadata => { Assert.Equal(200, metadata.StatusCode); - Assert.NotNull(metadata.Attribute); - Assert.Null(metadata.Convention); + Assert.Same(method, metadata.AttributeSource); }); } diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs new file mode 100644 index 0000000000..e1b19d61a7 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs @@ -0,0 +1,35 @@ + +[assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))] + +namespace TestApp._INPUT_ +{ + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [Route("[controller]/[action]")] + public class BaseController : ControllerBase + { + + } +} + +namespace TestApp._INPUT_ +{ + public class CodeFixAddsFullyQualifiedProducesResponseType : BaseController + { + public object GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(); + } + + return Accepted(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs new file mode 100644 index 0000000000..67c467796b --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs @@ -0,0 +1,39 @@ + +[assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))] + +namespace TestApp._OUTPUT_ +{ + using Microsoft.AspNetCore.Mvc; + + [ApiController] + [Route("[controller]/[action]")] + public class BaseController : ControllerBase + { + + } +} + +namespace TestApp._OUTPUT_ +{ + public class CodeFixAddsFullyQualifiedProducesResponseType : BaseController + { + [Microsoft.AspNetCore.Mvc.ProducesResponseType(202)] + [Microsoft.AspNetCore.Mvc.ProducesResponseType(400)] + [Microsoft.AspNetCore.Mvc.ProducesResponseType(404)] + [Microsoft.AspNetCore.Mvc.ProducesDefaultResponseType] + public object GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(); + } + + return Accepted(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs new file mode 100644 index 0000000000..708fd10db9 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs @@ -0,0 +1,23 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsMissingStatusCodes : ControllerBase + { + [ProducesResponseType(404)] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs new file mode 100644 index 0000000000..b0f2b96dc6 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs @@ -0,0 +1,26 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsMissingStatusCodes : ControllerBase + { + [ProducesResponseType(404)] + [ProducesResponseType(200)] + [ProducesResponseType(400)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs new file mode 100644 index 0000000000..39ae582dfd --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesController : ControllerBase + { + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs new file mode 100644 index 0000000000..690618c254 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs @@ -0,0 +1,20 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesController : ControllerBase + { + [ProducesResponseType(200)] + [ProducesResponseType(404)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs new file mode 100644 index 0000000000..63dc63e6c2 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsSuccessStatusCode : ControllerBase + { + public ActionResult GetItem(string id) + { + if (!int.TryParse(id, out var idInt)) + { + return BadRequest(); + } + + if (idInt == 0) + { + return NotFound(); + } + + return Created("url", new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs new file mode 100644 index 0000000000..2bc2dae2af --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs @@ -0,0 +1,30 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsSuccessStatusCode : ControllerBase + { + [ProducesResponseType(201)] + [ProducesResponseType(400)] + [ProducesResponseType(404)] + [ProducesDefaultResponseType] + public ActionResult GetItem(string id) + { + if (!int.TryParse(id, out var idInt)) + { + return BadRequest(); + } + + if (idInt == 0) + { + return NotFound(); + } + + return Created("url", new object()); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs new file mode 100644 index 0000000000..e907935bf4 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase + { + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Accepted("Result"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs new file mode 100644 index 0000000000..14c1a06e29 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs @@ -0,0 +1,24 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiConventionType(typeof(DefaultApiConventions))] + +namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase + { + [ProducesResponseType(202)] + [ProducesResponseType(404)] + [ProducesDefaultResponseType] + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Accepted("Result"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs new file mode 100644 index 0000000000..960c9f8450 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs @@ -0,0 +1,18 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase + { + [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Find))] + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Accepted("Result"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs new file mode 100644 index 0000000000..e5c384bf06 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs @@ -0,0 +1,20 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase + { + [ProducesResponseType(202)] + [ProducesResponseType(404)] + [ProducesDefaultResponseType] + public ActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Accepted("Result"); + } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs index a338f55d1a..fa5b642f2e 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs @@ -1,6 +1,7 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Analyzers; -[assembly: ApiConventionType(typeof(DefaultApiConventions))] +[assembly: ApiConventionType(typeof(DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCodeConvention))] namespace Microsoft.AspNetCore.Mvc.Analyzers { @@ -22,4 +23,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return Ok(); } } + + public static class DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCodeConvention + { + [ProducesResponseType(200)] + [ProducesResponseType(404)] + public static void Get(int id) { } + } } From 6d9aa281c57b0781d8024d8e1739b2aeb97cc6f7 Mon Sep 17 00:00:00 2001 From: Artak Mkrtchyan Date: Tue, 17 Jul 2018 14:28:53 -0700 Subject: [PATCH 154/316] Render `maxlength` attribute for an input tag, when MaxLength or StringLength validation attributes are applied to the model class. --- .../MvcViewOptions.cs | 16 ++ .../ViewFeatures/DefaultHtmlGenerator.cs | 62 ++++++ ...iewOptionsConfigureCompatibilityOptions.cs | 5 + .../ViewFeatures/DefaultHtmlGeneratorTest.cs | 184 +++++++++++++++++- 4 files changed, 266 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs index 9d0627da96..237777da23 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.ViewEngines; @@ -17,15 +18,18 @@ namespace Microsoft.AspNetCore.Mvc public class MvcViewOptions : IEnumerable { private readonly CompatibilitySwitch _suppressTempDataAttributePrefix; + private readonly CompatibilitySwitch _allowRenderingMaxLengthAttribute; private readonly ICompatibilitySwitch[] _switches; private HtmlHelperOptions _htmlHelperOptions = new HtmlHelperOptions(); public MvcViewOptions() { _suppressTempDataAttributePrefix = new CompatibilitySwitch(nameof(SuppressTempDataAttributePrefix)); + _allowRenderingMaxLengthAttribute = new CompatibilitySwitch(nameof(AllowRenderingMaxLengthAttribute)); _switches = new[] { _suppressTempDataAttributePrefix, + _allowRenderingMaxLengthAttribute }; } @@ -87,6 +91,18 @@ namespace Microsoft.AspNetCore.Mvc set => _suppressTempDataAttributePrefix.Value = value; } + /// + /// Gets or sets a value that indicates whether the maxlength attribute should be rendered for compatible HTML elements, + /// when they're bound to models marked with either + /// or attributes. + /// + /// If both attributes are specified, the one with the smaller value will be used for the rendered `maxlength` attribute. + public bool AllowRenderingMaxLengthAttribute + { + get => _allowRenderingMaxLengthAttribute.Value; + set => _allowRenderingMaxLengthAttribute.Value = value; + } + /// /// Gets a list s used by this application. /// diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs index e502341da4..d0121241b6 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Globalization; using System.Linq; @@ -30,6 +31,10 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private static readonly string[] _placeholderInputTypes = new[] { "text", "search", "url", "tel", "email", "password", "number" }; + // See: (http://www.w3.org/TR/html5/sec-forms.html#apply) + private static readonly string[] _maxLengthInputTypes = + new[] { "text", "search", "url", "tel", "email", "password" }; + private readonly IAntiforgery _antiforgery; private readonly IModelMetadataProvider _metadataProvider; private readonly IUrlHelperFactory _urlHelperFactory; @@ -92,8 +97,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures // Underscores are fine characters in id's. IdAttributeDotReplacement = optionsAccessor.Value.HtmlHelperOptions.IdAttributeDotReplacement; + + AllowRenderingMaxLengthAttribute = optionsAccessor.Value.AllowRenderingMaxLengthAttribute; } + /// + /// Gets or sets a value that indicates whether the maxlength attribute should be rendered for compatible HTML input elements, + /// when they're bound to models marked with either + /// or attributes. + /// + /// If both attributes are specified, the one with the smaller value will be used for the rendered `maxlength` attribute. + protected bool AllowRenderingMaxLengthAttribute { get; } + /// public string IdAttributeDotReplacement { get; } @@ -724,6 +739,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); + if (AllowRenderingMaxLengthAttribute) + { + AddMaxLengthAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); + } + AddValidationAttributes(viewContext, tagBuilder, modelExplorer, expression); // If there are any errors for a named field, we add this CSS attribute. @@ -1239,6 +1259,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures AddPlaceholderAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); } + if (AllowRenderingMaxLengthAttribute && _maxLengthInputTypes.Contains(suppliedTypeString)) + { + AddMaxLengthAttribute(viewContext.ViewData, tagBuilder, modelExplorer, expression); + } + var valueParameter = FormatValue(value, format); var usedModelState = false; switch (inputType) @@ -1373,6 +1398,43 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } } + /// + /// Adds a maxlength attribute to the . + /// + /// A instance for the current scope. + /// A instance. + /// The for the . + /// Expression name, relative to the current model. + protected virtual void AddMaxLengthAttribute( + ViewDataDictionary viewData, + TagBuilder tagBuilder, + ModelExplorer modelExplorer, + string expression) + { + modelExplorer = modelExplorer ?? ExpressionMetadataProvider.FromStringExpression( + expression, + viewData, + _metadataProvider); + + int? maxLengthValue = null; + foreach (var attribute in modelExplorer.Metadata.ValidatorMetadata) + { + if (attribute is MaxLengthAttribute maxLengthAttribute && (!maxLengthValue.HasValue || maxLengthValue.Value > maxLengthAttribute.Length)) + { + maxLengthValue = maxLengthAttribute.Length; + } + else if (attribute is StringLengthAttribute stringLengthAttribute && (!maxLengthValue.HasValue || maxLengthValue.Value > stringLengthAttribute.MaximumLength)) + { + maxLengthValue = stringLengthAttribute.MaximumLength; + } + } + + if (maxLengthValue.HasValue) + { + tagBuilder.MergeAttribute("maxlength", maxLengthValue.Value.ToString()); + } + } + /// /// Adds validation attributes to the if client validation /// is enabled. diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs index 81de59fe12..b17210f7de 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs @@ -28,6 +28,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures values[nameof(MvcViewOptions.SuppressTempDataAttributePrefix)] = true; } + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(MvcViewOptions.AllowRenderingMaxLengthAttribute)] = true; + } + return values; } } diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs index d4e5477aab..0d38ca3674 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; using System.Text.Encodings.Web; @@ -190,6 +191,169 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures Assert.Equal(expected, attribute.Value); } + [Theory] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)] + public void GenerateTextArea_RendersMaxLength(string expression, int expectedValue) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GenerateTextArea(viewContext, modelExplorer, expression, rows: 1, columns: 1, htmlAttributes); + + // Assert + var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength"); + Assert.Equal(expectedValue, Int32.Parse(attribute.Value)); + } + + [Theory] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)] + public void GeneratePassword_RendersMaxLength(string expression, int expectedValue) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GeneratePassword(viewContext, modelExplorer, expression, null, htmlAttributes); + + // Assert + var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength"); + Assert.Equal(expectedValue, Int32.Parse(attribute.Value)); + } + + [Theory] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)] + public void GenerateTextBox_RendersMaxLength(string expression, int expectedValue) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, expression, null, null, htmlAttributes); + + // Assert + var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength"); + Assert.Equal(expectedValue, Int32.Parse(attribute.Value)); + } + + [Fact] + public void GenerateTextBox_RendersMaxLength_WithMinimumValueFromBothAttributes() + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), nameof(ModelWithMaxLengthMetadata.FieldWithBothAttributes)); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, nameof(ModelWithMaxLengthMetadata.FieldWithBothAttributes), null, null, htmlAttributes); + + // Assert + var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength"); + Assert.Equal(Math.Min(ModelWithMaxLengthMetadata.MaxLengthAttributeValue, ModelWithMaxLengthMetadata.StringLengthAttributeValue), Int32.Parse(attribute.Value)); + } + + [Fact] + public void GenerateTextBox_DoesNotRenderMaxLength_WhenNoAttributesPresent() + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), nameof(ModelWithMaxLengthMetadata.FieldWithoutAttributes)); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, nameof(ModelWithMaxLengthMetadata.FieldWithoutAttributes), null, null, htmlAttributes); + + // Assert + Assert.DoesNotContain(tagBuilder.Attributes, a => a.Key == "maxlength"); + } + + [Theory] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength), ModelWithMaxLengthMetadata.MaxLengthAttributeValue)] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength), ModelWithMaxLengthMetadata.StringLengthAttributeValue)] + public void GenerateTextBox_SearchType_RendersMaxLength(string expression, int expectedValue) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + { "type", "search"} + }; + + // Act + var tagBuilder = htmlGenerator.GenerateTextBox(viewContext, modelExplorer, expression, null, null, htmlAttributes); + + // Assert + var attribute = Assert.Single(tagBuilder.Attributes, a => a.Key == "maxlength"); + Assert.Equal(expectedValue, Int32.Parse(attribute.Value)); + } + + [Theory] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithMaxLength))] + [InlineData(nameof(ModelWithMaxLengthMetadata.FieldWithStringLength))] + public void GenerateHidden_DoesNotRenderMaxLength(string expression) + { + // Arrange + var metadataProvider = new TestModelMetadataProvider(); + var htmlGenerator = GetGenerator(metadataProvider); + var viewContext = GetViewContext(model: null, metadataProvider: metadataProvider); + var modelMetadata = metadataProvider.GetMetadataForProperty(typeof(ModelWithMaxLengthMetadata), expression); + var modelExplorer = new ModelExplorer(metadataProvider, modelMetadata, null); + var htmlAttributes = new Dictionary + { + { "name", "testElement" }, + }; + + // Act + var tagBuilder = htmlGenerator.GenerateHidden(viewContext, modelExplorer, expression, null, false, htmlAttributes); + + // Assert + Assert.DoesNotContain(tagBuilder.Attributes, a => a.Key == "maxlength"); + } + [Fact] public void GenerateValidationMessage_WithNullExpression_Throws() { @@ -759,7 +923,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private static IHtmlGenerator GetGenerator(IModelMetadataProvider metadataProvider) { var mvcViewOptionsAccessor = new Mock>(); - mvcViewOptionsAccessor.SetupGet(accessor => accessor.Value).Returns(new MvcViewOptions()); + mvcViewOptionsAccessor.SetupGet(accessor => accessor.Value).Returns(new MvcViewOptions() { AllowRenderingMaxLengthAttribute = true }); var htmlEncoder = Mock.Of(); var antiforgery = new Mock(); @@ -820,6 +984,24 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures All = -1, } + private class ModelWithMaxLengthMetadata + { + internal const int MaxLengthAttributeValue = 77; + internal const int StringLengthAttributeValue = 7; + + [MaxLength(MaxLengthAttributeValue)] + public string FieldWithMaxLength { get; set; } + + [StringLength(StringLengthAttributeValue)] + public string FieldWithStringLength { get; set; } + + public string FieldWithoutAttributes { get; set; } + + [MaxLength(MaxLengthAttributeValue)] + [StringLength(StringLengthAttributeValue)] + public string FieldWithBothAttributes { get; set; } + } + private class Model { public int Id { get; set; } From 5a200379651e752b426057639792d0f8e603faf6 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 2 Aug 2018 11:08:39 -0700 Subject: [PATCH 155/316] Move API convention analyzers to Microsoft.AspNetCore.Mvc.Api.Analyzers Fixes #8153 --- Mvc.NoFun.sln | 35 ++++++++++++- Mvc.sln | 15 ++++++ .../CodeAnalysisExtensions.cs | 3 +- .../DiagnosticDescriptors.cs | 38 -------------- .../ActualApiResponseMetadata.cs | 2 +- .../AddResponseTypeAttributeCodeFixAction.cs | 6 +-- ...AddResponseTypeAttributeCodeFixProvider.cs | 10 ++-- ...ireExplicitModelValidationCheckAnalyzer.cs | 6 +-- ...eExplicitModelValidationCodeFixProvider.cs | 6 +-- .../ApiControllerFacts.cs | 7 ++- .../ApiControllerSymbolCache.cs | 34 ++++++------- .../ApiConventionAnalyzer.cs | 14 +++--- .../ApiDiagnosticDescriptors.cs | 48 ++++++++++++++++++ .../ApiSymbolNames.cs | 36 +++++++++++++ .../DeclaredApiResponseMetadata.cs | 2 +- ...rosoft.AspNetCore.Mvc.Api.Analyzers.csproj | 50 +++++++++++++++++++ ...rosoft.AspNetCore.Mvc.Api.Analyzers.nuspec | 24 +++++++++ .../MvcFacts.cs | 2 +- .../Properties/AssemblyInfo.cs | 6 +++ .../SymbolApiConventionMatcher.cs | 2 +- .../SymbolApiResponseMetadataProvider.cs | 2 +- ...ouldNotBeAppliedToPageModelAnalyzerTest.cs | 1 - .../AvoidHtmlPartialAnalyzerTest.cs | 1 - .../CodeAnalysisExtensionsTest.cs | 1 - .../MvcDiagnosticAnalyzerRunner.cs | 2 +- .../Infrastructure/MvcTestSource.cs | 2 +- ...AttributeCodeFixProviderIntegrationTest.cs | 4 +- ...lValidationCheckAnalyzerIntegrationTest.cs | 5 +- ...ModelValidationCheckCodeFixProviderTest.cs | 3 +- .../ApiControllerFactsTest.cs | 3 +- .../ApiConventionAnalyzerIntegrationTest.cs | 31 ++++++------ .../Mvc.Api.Analyzers.Test.csproj | 22 ++++++++ .../MvcFactsTest.cs | 3 +- .../SymbolApiConventionMatcherTest.cs | 5 +- .../SymbolApiResponseMetadataProviderTest.cs | 5 +- ...ullyQualifiedProducesResponseType.Input.cs | 0 ...llyQualifiedProducesResponseType.Output.cs | 0 .../CodeFixAddsMissingStatusCodes.Input.cs | 2 +- .../CodeFixAddsMissingStatusCodes.Output.cs | 2 +- .../CodeFixAddsStatusCodes.Input.cs | 2 +- .../CodeFixAddsStatusCodes.Output.cs | 2 +- .../CodeFixAddsSuccessStatusCode.Input.cs | 2 +- .../CodeFixAddsSuccessStatusCode.Output.cs | 2 +- ...hConventionAddsMissingStatusCodes.Input.cs | 2 +- ...ConventionAddsMissingStatusCodes.Output.cs | 2 +- ...ntionMethodAddsMissingStatusCodes.Input.cs | 2 +- ...tionMethodAddsMissingStatusCodes.Output.cs | 2 +- ...urned_ForApiActionsWithModelStateChecks.cs | 2 +- ...ctionsWithModelStateChecksUsingEquality.cs | 2 +- ...tionsWithModelStateChecksWithoutBracing.cs | 2 +- ...rApiActionsCheckingAdditionalConditions.cs | 2 +- ...urning400FromNonModelStateIsValidBlocks.cs | 2 +- ...ningNot400FromNonModelStateIsValidBlock.cs | 2 +- ...ed_ForApiActionsWithoutModelStateChecks.cs | 2 +- ...gnosticsAreReturned_ForNonApiController.cs | 2 +- ...agnosticsAreReturned_ForRazorPageModels.cs | 2 +- ...odeFixRemovesIfBlockWithoutBraces.Input.cs | 2 +- ...deFixRemovesIfBlockWithoutBraces.Output.cs | 2 +- ...teIsInvalidBlockWithEqualityCheck.Input.cs | 2 +- ...eIsInvalidBlockWithEqualityCheck.Output.cs | 2 +- ...StateIsInvalidBlockWithIfNotCheck.Input.cs | 2 +- ...tateIsInvalidBlockWithIfNotCheck.Output.cs | 2 +- .../ApiControllerFactsTest/TestFile.cs | 2 +- ...tOfTReturningMethodWithoutAnyAttributes.cs | 2 +- ...OfTReturningMethodWithoutSomeAttributes.cs | 2 +- ...urned_ForControllerWithCustomConvention.cs | 4 +- ...Attribute_ReturnsUndocumentedStatusCode.cs | 2 +- ...Attribute_ReturnsUndocumentedStatusCode.cs | 2 +- ...ionMethod_ReturnsUndocumentedStatusCode.cs | 2 +- ...nouslyReturnsValue_WithoutDocumentation.cs | 2 +- ...ributeReturnsValue_WithoutDocumentation.cs | 2 +- ...fMethodWithAttribute_ReturnsDerivedType.cs | 2 +- ...ntion_DoesNotReturnDocumentedStatusCode.cs | 2 +- ...onvention_ReturnsUndocumentedStatusCode.cs | 4 +- ...ribute_DoesNotDocumentSuccessStatusCode.cs | 2 +- ...ibute_DoesNotReturnDocumentedStatusCode.cs | 2 +- ...Attribute_ReturnsUndocumentedStatusCode.cs | 2 +- ...ontroller_IfStatusCodesCannotBeInferred.cs | 2 +- ...Controller_WithAllDocumentedStatusCodes.cs | 2 +- ...gnosticsAreReturned_ForNonApiController.cs | 2 +- ...agnosticsAreReturned_ForRazorPageModels.cs | 2 +- ...reReturned_ForReturnStatementsInLambdas.cs | 2 +- ...ned_ForReturnStatementsInLocalFunctions.cs | 2 +- .../MvcFactsTest/IsControllerActionTests.cs | 2 +- .../MvcFactsTest/IsControllerTests.cs | 2 +- .../SymbolApiConventionMatcherTestFile.cs | 2 +- .../GetDefaultStatusCodeTest.cs | 2 +- .../GetResponseMetadataTests.cs | 2 +- ...etadata_IfReturnedTypeIsNotActionResult.cs | 2 +- ...efaultStatusCodeAttributeOnActionResult.cs | 2 +- 90 files changed, 362 insertions(+), 184 deletions(-) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ActualApiResponseMetadata.cs (94%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/AddResponseTypeAttributeCodeFixAction.cs (96%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/AddResponseTypeAttributeCodeFixProvider.cs (70%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs (97%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs (88%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ApiControllerFacts.cs (79%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ApiControllerSymbolCache.cs (68%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/ApiConventionAnalyzer.cs (88%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/DeclaredApiResponseMetadata.cs (98%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/MvcFacts.cs (98%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/SymbolApiConventionMatcher.cs (99%) rename src/{Microsoft.AspNetCore.Mvc.Analyzers => Microsoft.AspNetCore.Mvc.Api.Analyzers}/SymbolApiResponseMetadataProvider.cs (99%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs (95%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs (93%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs (95%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/ApiControllerFactsTest.cs (97%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/ApiConventionAnalyzerIntegrationTest.cs (79%) create mode 100644 test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/MvcFactsTest.cs (99%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/SymbolApiConventionMatcherTest.cs (99%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/SymbolApiResponseMetadataProviderTest.cs (99%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs (100%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs (100%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs (84%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs (87%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs (92%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs (76%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs (77%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs (74%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs (75%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs (72%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs (73%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs (69%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs (67%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs (68%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs (72%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs (67%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs (76%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs (70%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs (75%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs (70%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiControllerFactsTest/TestFile.cs (92%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs (92%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs (92%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs (91%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs (91%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs (94%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs (92%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs (94%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs (89%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs (90%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs (91%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs (85%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs (93%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs (87%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs (86%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs (94%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs (93%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/MvcFactsTest/IsControllerActionTests.cs (97%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/MvcFactsTest/IsControllerTests.cs (95%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs (97%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs (87%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs (98%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs (88%) rename test/{Mvc.Analyzers.Test => Mvc.Api.Analyzers.Test}/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs (81%) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 445f77a303..d8e44d40bf 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -1,4 +1,5 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 + +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 VisualStudioVersion = 15.0.27130.2036 MinimumVisualStudioVersion = 15.0.26730.03 @@ -114,7 +115,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{92D959F2-66B8-490A-BA33-DA4421EBC948}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Testing", "src\Microsoft.AspNetCore.Mvc.Testing\Microsoft.AspNetCore.Mvc.Testing.csproj", "{92D959F2-66B8-490A-BA33-DA4421EBC948}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{1B398182-9EAE-400B-A2BD-EFFAC0168A36}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{71C626FC-6408-494B-A127-38CB64F71324}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -574,6 +579,30 @@ Global {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|Mixed Platforms.Build.0 = Release|Any CPU {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.ActiveCfg = Release|Any CPU {92D959F2-66B8-490A-BA33-DA4421EBC948}.Release|x86.Build.0 = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|x86.ActiveCfg = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Debug|x86.Build.0 = Debug|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|Any CPU.Build.0 = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|x86.ActiveCfg = Release|Any CPU + {1B398182-9EAE-400B-A2BD-EFFAC0168A36}.Release|x86.Build.0 = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|Any CPU.Build.0 = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|x86.ActiveCfg = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Debug|x86.Build.0 = Debug|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|Any CPU.ActiveCfg = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|Any CPU.Build.0 = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.ActiveCfg = Release|Any CPU + {71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -618,6 +647,8 @@ Global {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {1B398182-9EAE-400B-A2BD-EFFAC0168A36} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {71C626FC-6408-494B-A127-38CB64F71324} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A} diff --git a/Mvc.sln b/Mvc.sln index 77f80bb3e4..8223801973 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -176,6 +176,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicApi", "benchmarkapps\B EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps\BasicViews\BasicViews.csproj", "{E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -924,6 +926,18 @@ Global {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|Mixed Platforms.Build.0 = Release|Any CPU {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|x86.ActiveCfg = Release|Any CPU {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB}.Release|x86.Build.0 = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|x86.ActiveCfg = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Debug|x86.Build.0 = Debug|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|Any CPU.Build.0 = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|x86.ActiveCfg = Release|Any CPU + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -995,6 +1009,7 @@ Global {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {910F023A-88E3-4CB4-8793-AC4005C7B421} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} + {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs index 4211409bf4..7856b27b02 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -4,9 +4,8 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.CodeAnalysis { internal static class CodeAnalysisExtensions { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs index f8c1b7bb72..b2684297bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs @@ -42,43 +42,5 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC1004_ActionReturnsUndocumentedStatusCode = - new DiagnosticDescriptor( - "MVC1004", - "Action returns undeclared status code.", - "Action method returns undeclared status code '{0}'.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC1005_ActionReturnsUndocumentedSuccessResult = - new DiagnosticDescriptor( - "MVC1005", - "Action returns undeclared success result.", - "Action method returns a success result without a corresponding ProducesResponseType.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC1006_ActionDoesNotReturnDocumentedStatusCode = - new DiagnosticDescriptor( - "MVC1006", - "Action documents status code that is not returned.", - "Action method documents status code '{0}' without a corresponding return type.", - "Usage", - DiagnosticSeverity.Info, - isEnabledByDefault: false); - - public static readonly DiagnosticDescriptor MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck = - new DiagnosticDescriptor( - "MVC1007", - "Action methods on ApiController instances do not require explicit model validation check.", - "Action methods on ApiController instances do not require explicit model validation check.", - "Usage", - DiagnosticSeverity.Info, - isEnabledByDefault: true, - customTags: new[] { WellKnownDiagnosticTags.Unnecessary }); - } } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs similarity index 94% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs index 3772f290e7..15f790450e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ActualApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs @@ -3,7 +3,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal readonly struct ActualApiResponseMetadata { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs similarity index 96% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs index 56f2373203..27a7ca1c35 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixAction.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs @@ -13,7 +13,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Simplification; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal sealed class AddResponseTypeAttributeCodeFixAction : CodeAction { @@ -108,7 +108,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private static AttributeSyntax CreateProducesResponseTypeAttribute(int statusCode) { return SyntaxFactory.Attribute( - SyntaxFactory.ParseName(SymbolNames.ProducesResponseTypeAttribute) + SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute) .WithAdditionalAnnotations(Simplifier.Annotation), SyntaxFactory.AttributeArgumentList().AddArguments( SyntaxFactory.AttributeArgument( @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private static AttributeSyntax CreateProducesDefaultResponseTypeAttribute() { return SyntaxFactory.Attribute( - SyntaxFactory.ParseName(SymbolNames.ProducesDefaultResponseTypeAttribute) + SyntaxFactory.ParseName(ApiSymbolNames.ProducesDefaultResponseTypeAttribute) .WithAdditionalAnnotations(Simplifier.Annotation)); } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs similarity index 70% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs index fc69730061..a5a2a62613 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs @@ -7,15 +7,15 @@ using System.Threading.Tasks; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CodeFixes; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ExportCodeFixProvider(LanguageNames.CSharp)] [Shared] public class AddResponseTypeAttributeCodeFixProvider : CodeFixProvider { public override ImmutableArray FixableDiagnosticIds => ImmutableArray.Create( - DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id, - DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id); + ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id, + ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult.Id); public sealed override Task RegisterCodeFixesAsync(CodeFixContext context) { @@ -25,8 +25,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } var diagnostic = context.Diagnostics[0]; - if ((diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode.Id) && - (diagnostic.Descriptor.Id != DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult.Id)) + if ((diagnostic.Descriptor.Id != ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id) && + (diagnostic.Descriptor.Id != ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult.Id)) { return Task.CompletedTask; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs similarity index 97% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs index 0408513ced..e8b52c012e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -8,13 +8,13 @@ using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; using Microsoft.CodeAnalysis.Operations; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( - DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck); + ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck); public override void Initialize(AnalysisContext context) { @@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers operationAnalysisContext.ReportDiagnostic( Diagnostic.Create( - DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck, + ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck, ifStatement.GetLocation(), additionalLocations: additionalLocations)); }, OperationKind.Conditional); diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs similarity index 88% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs index 3446de28a4..a9439c6884 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs @@ -11,14 +11,14 @@ using Microsoft.CodeAnalysis.CodeFixes; using Microsoft.CodeAnalysis.Editing; using Microsoft.CodeAnalysis.Text; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ExportCodeFixProvider(LanguageNames.CSharp)] [Shared] public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProvider : CodeFixProvider { public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck.Id); + ImmutableArray.Create(ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck.Id); public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } var diagnostic = context.Diagnostics[0]; - if (diagnostic.Id != DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck.Id) + if (diagnostic.Id != ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck.Id) { return Task.CompletedTask; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs similarity index 79% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs index 48d81975ef..b947b8dc5d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs @@ -1,6 +1,9 @@ -using Microsoft.CodeAnalysis; +// 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.Analyzers +using Microsoft.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal static class ApiControllerFacts { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs similarity index 68% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs index 56097763d5..c2a6f167d2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs @@ -4,35 +4,31 @@ using System; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal readonly struct ApiControllerSymbolCache { public ApiControllerSymbolCache(Compilation compilation) { - ActionResultOfT = compilation.GetTypeByMetadataName(SymbolNames.ActionResultOfT); - ApiConventionMethodAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionMethodAttribute); - ApiConventionNameMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionNameMatchAttribute); - ApiConventionTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeAttribute); - ApiConventionTypeMatchAttribute = compilation.GetTypeByMetadataName(SymbolNames.ApiConventionTypeMatchAttribute); - ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute); - DefaultStatusCodeAttribute = compilation.GetTypeByMetadataName(SymbolNames.DefaultStatusCodeAttribute); - IActionResult = compilation.GetTypeByMetadataName(SymbolNames.IActionResult); - IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata); - IConvertToActionResult = compilation.GetTypeByMetadataName(SymbolNames.IConvertToActionResult); - ModelStateDictionary = compilation.GetTypeByMetadataName(SymbolNames.ModelStateDictionary); - NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); - NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); - ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesDefaultResponseTypeAttribute); - ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(SymbolNames.ProducesResponseTypeAttribute); + ApiConventionMethodAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ApiConventionMethodAttribute); + ApiConventionNameMatchAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ApiConventionNameMatchAttribute); + ApiConventionTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ApiConventionTypeAttribute); + ApiConventionTypeMatchAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ApiConventionTypeMatchAttribute); + ControllerAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ControllerAttribute); + DefaultStatusCodeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.DefaultStatusCodeAttribute); + IActionResult = compilation.GetTypeByMetadataName(ApiSymbolNames.IActionResult); + IApiBehaviorMetadata = compilation.GetTypeByMetadataName(ApiSymbolNames.IApiBehaviorMetadata); + ModelStateDictionary = compilation.GetTypeByMetadataName(ApiSymbolNames.ModelStateDictionary); + NonActionAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.NonActionAttribute); + NonControllerAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.NonControllerAttribute); + ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesDefaultResponseTypeAttribute); + ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesResponseTypeAttribute); var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); var members = disposable.GetMembers(nameof(IDisposable.Dispose)); IDisposableDispose = members.Length == 1 ? (IMethodSymbol)members[0] : null; } - public INamedTypeSymbol ActionResultOfT { get; } - public INamedTypeSymbol ApiConventionMethodAttribute { get; } public INamedTypeSymbol ApiConventionNameMatchAttribute { get; } @@ -49,8 +45,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public INamedTypeSymbol IApiBehaviorMetadata { get; } - public INamedTypeSymbol IConvertToActionResult { get; } - public IMethodSymbol IDisposableDispose { get; } public ITypeSymbol ModelStateDictionary { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs similarity index 88% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs index 39d810f42e..27e5439330 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/ApiConventionAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs @@ -8,15 +8,15 @@ using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; using Microsoft.CodeAnalysis.Diagnostics; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [DiagnosticAnalyzer(LanguageNames.CSharp)] public class ApiConventionAnalyzer : DiagnosticAnalyzer { public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create( - DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, - DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, - DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode); + ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, + ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult, + ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode); public override void Initialize(AnalysisContext context) { @@ -64,13 +64,13 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers if (actualMetadata.IsDefaultResponse) { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult, + ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult, location)); } else { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, + ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, location, actualMetadata.StatusCode)); } @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers if (!Contains(actualResponseMetadata, declaredMetadata)) { syntaxNodeContext.ReportDiagnostic(Diagnostic.Create( - DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, + ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, methodSyntax.Identifier.GetLocation(), declaredMetadata.StatusCode)); } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs new file mode 100644 index 0000000000..ff56f64f1c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs @@ -0,0 +1,48 @@ +// 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.CodeAnalysis; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + internal static class ApiDiagnosticDescriptors + { + public static readonly DiagnosticDescriptor API1000_ActionReturnsUndocumentedStatusCode = + new DiagnosticDescriptor( + "API1000", + "Action returns undeclared status code.", + "Action method returns undeclared status code '{0}'.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor API1001_ActionReturnsUndocumentedSuccessResult = + new DiagnosticDescriptor( + "API1001", + "Action returns undeclared success result.", + "Action method returns a success result without a corresponding ProducesResponseType.", + "Usage", + DiagnosticSeverity.Warning, + isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor API1002_ActionDoesNotReturnDocumentedStatusCode = + new DiagnosticDescriptor( + "API1002", + "Action documents status code that is not returned.", + "Action method documents status code '{0}' without a corresponding return type.", + "Usage", + DiagnosticSeverity.Info, + isEnabledByDefault: false); + + public static readonly DiagnosticDescriptor API1003_ApiActionsDoNotRequireExplicitModelValidationCheck = + new DiagnosticDescriptor( + "API1003", + "Action methods on ApiController instances do not require explicit model validation check.", + "Action methods on ApiController instances do not require explicit model validation check.", + "Usage", + DiagnosticSeverity.Info, + isEnabledByDefault: true, + customTags: new[] { WellKnownDiagnosticTags.Unnecessary }); + + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs new file mode 100644 index 0000000000..9ec9bdfa0f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs @@ -0,0 +1,36 @@ +// 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.Api.Analyzers +{ + internal static class ApiSymbolNames + { + public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; + + public const string ApiConventionMethodAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute"; + + public const string ApiConventionNameMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionNameMatchAttribute"; + + public const string ApiConventionTypeMatchAttribute = "Microsoft.AspNetCore.Mvc.ApiExplorer.ApiConventionTypeMatchAttribute"; + + public const string ApiConventionTypeAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionTypeAttribute"; + + public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; + + public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; + + public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; + + public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; + + public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; + + public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; + + public const string NonControllerAttribute = "Microsoft.AspNetCore.Mvc.NonControllerAttribute"; + + public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute"; + + public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs similarity index 98% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs index e2cbd6c994..a45b1ed573 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DeclaredApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs @@ -4,7 +4,7 @@ using System.Collections.Generic; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal readonly struct DeclaredApiResponseMetadata { diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj new file mode 100644 index 0000000000..5081b0d502 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj @@ -0,0 +1,50 @@ + + + CSharp Analyzers for ASP.NET Core MVC. + aspnetcore;aspnetcoremvc + + netstandard1.3 + false + false + false + $(MSBuildProjectName).nuspec + + + + + + + + + + + + + + + + + + + true + + + id=$(PackageId); + version=$(PackageVersion); + authors=$(Authors); + description=$(Description); + tags=$(PackageTags.Replace(';', ' ')); + licenseUrl=$(PackageLicenseUrl); + projectUrl=$(PackageProjectUrl); + iconUrl=$(PackageIconUrl); + repositoryUrl=$(RepositoryUrl); + repositoryCommit=$(RepositoryCommit); + copyright=$(Copyright); + + OutputBinary=$(OutputPath)$(AssemblyName).dll; + OutputSymbol=$(OutputPath)$(AssemblyName).pdb; + + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec new file mode 100644 index 0000000000..5f9d436f73 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec @@ -0,0 +1,24 @@ + + + + $id$ + $version$ + $authors$ + true + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $description$ + $copyright$ + $tags$ + + + + + + + + + + + diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs similarity index 98% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs index 90651247c4..a5dfe71dcc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal static class MvcFacts { diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..1ed7ae5ed2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Mvc.Api.Analyzers.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs similarity index 99% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs index 878d0189e9..78078cfcd0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiConventionMatcher.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs @@ -5,7 +5,7 @@ using System; using System.Linq; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal static class SymbolApiConventionMatcher { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs similarity index 99% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs index 545955782f..5de70bae0a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp; using Microsoft.CodeAnalysis.CSharp.Syntax; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal static class SymbolApiResponseMetadataProvider { diff --git a/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs b/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs index 1bb389e166..baac0e3b4f 100644 --- a/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs +++ b/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; diff --git a/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs b/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs index b6de06c5fa..cac606e97c 100644 --- a/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs +++ b/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs @@ -4,7 +4,6 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; diff --git a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs index 6c5a5a2937..d29b1a2f2b 100644 --- a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs +++ b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; diff --git a/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs b/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs index 52a18845f0..f29cff84d1 100644 --- a/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs +++ b/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Diagnostics; -namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure +namespace Microsoft.AspNetCore.Mvc { public class MvcDiagnosticAnalyzerRunner : DiagnosticAnalyzerRunner { diff --git a/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs b/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs index 19c19140d1..e0e2df6493 100644 --- a/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs +++ b/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs @@ -5,7 +5,7 @@ using System.IO; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Testing; -namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure +namespace Microsoft.AspNetCore.Mvc { public static class MvcTestSource { diff --git a/test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs similarity index 95% rename from test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs rename to test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs index cfcf6f63f7..beea4d68a7 100644 --- a/test/Mvc.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs @@ -1,15 +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; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class AddResponseTypeAttributeCodeFixProviderIntegrationTest { diff --git a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs similarity index 93% rename from test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs rename to test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs index d2feb871ff..9b3e546014 100644 --- a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs @@ -4,10 +4,9 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { @@ -65,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers private async Task RunTest([CallerMemberName] string testMethod = "") { // Arrange - var descriptor = DiagnosticDescriptors.MVC1007_ApiActionsDoNotRequireExplicitModelValidationCheck; + var descriptor = ApiDiagnosticDescriptors.API1003_ApiActionsDoNotRequireExplicitModelValidationCheck; var testSource = MvcTestSource.Read(GetType().Name, testMethod); var expectedLocation = testSource.DefaultMarkerLocation; diff --git a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs b/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs similarity index 95% rename from test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs rename to test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs index d470e22112..918a04940a 100644 --- a/test/Mvc.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs @@ -4,11 +4,10 @@ using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest { diff --git a/test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs similarity index 97% rename from test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs rename to test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs index a264d33878..f096f51455 100644 --- a/test/Mvc.Analyzers.Test/ApiControllerFactsTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs @@ -4,11 +4,10 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiControllerFactsTest { diff --git a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs similarity index 79% rename from test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs rename to test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index 41ada495e3..13942b0046 100644 --- a/test/Mvc.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -5,11 +5,10 @@ using System; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiConventionAnalyzerIntegrationTest { @@ -41,55 +40,55 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers [Fact] public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404); [Fact] public Task DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404); [Fact] public Task DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 200); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 200); [Fact] public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 404); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404); [Fact] public Task DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 422); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 422); [Fact] public Task DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 400); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 400); [Fact] public Task DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1004_ActionReturnsUndocumentedStatusCode, 202); + => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 202); [Fact] public Task DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation() - => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult); [Fact] public Task DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation() - => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult); [Fact] public Task DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType() - => RunTest(DiagnosticDescriptors.MVC1005_ActionReturnsUndocumentedSuccessResult); + => RunTest(ApiDiagnosticDescriptors.API1001_ActionReturnsUndocumentedSuccessResult); [Fact] public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 400); + => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 400); [Fact] public Task DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode() - => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 404); + => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 404); [Fact] public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode() - => RunTest(DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode, 200); + => RunTest(ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode, 200); private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") { @@ -143,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers // 10006 is disabled by default. Explicitly enable it so we can correctly validate no diagnostics // are returned scenarios. var specificDiagnosticOptions = compilationOptions.SpecificDiagnosticOptions.Add( - DiagnosticDescriptors.MVC1006_ActionDoesNotReturnDocumentedStatusCode.Id, + ApiDiagnosticDescriptors.API1002_ActionDoesNotReturnDocumentedStatusCode.Id, ReportDiagnostic.Info); return compilationOptions.WithSpecificDiagnosticOptions(specificDiagnosticOptions); diff --git a/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj b/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj new file mode 100644 index 0000000000..76dffea5a6 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj @@ -0,0 +1,22 @@ + + + + $(StandardTestTfms) + Microsoft.AspNetCore.Mvc.Api.Analyzers + + + + + + + + + + + + + + + + + diff --git a/test/Mvc.Analyzers.Test/MvcFactsTest.cs b/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs similarity index 99% rename from test/Mvc.Analyzers.Test/MvcFactsTest.cs rename to test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs index 6cb3a518a7..e6009e7125 100644 --- a/test/Mvc.Analyzers.Test/MvcFactsTest.cs +++ b/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs @@ -5,11 +5,10 @@ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class MvcFactsTest { diff --git a/test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs similarity index 99% rename from test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs rename to test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs index 34441fc218..041d148624 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiConventionMatcherTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs @@ -5,12 +5,11 @@ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Xunit; -using static Microsoft.AspNetCore.Mvc.Analyzers.SymbolApiConventionMatcher; +using static Microsoft.AspNetCore.Mvc.Api.Analyzers.SymbolApiConventionMatcher; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class SymbolApiConventionMatcherTest { diff --git a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs similarity index 99% rename from test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs rename to test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index e31f22e8ce..e6ccc12470 100644 --- a/test/Mvc.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -6,12 +6,11 @@ using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class SymbolApiResponseMetadataProviderTest { @@ -395,7 +394,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers var source = @" using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs index 708fd10db9..a2992eb108 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs index b0f2b96dc6..136b633726 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs similarity index 84% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs index 39ae582dfd..7c3e027608 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs similarity index 87% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs index 690618c254..09e944a4b8 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs index 63dc63e6c2..ff32f9b56d 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs similarity index 92% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs index 2bc2dae2af..d51b75a841 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs index e907935bf4..55a1e429b7 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs index 14c1a06e29..94e564948d 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs index 960c9f8450..96e622e16b 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs index e5c384bf06..24be3e391c 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs similarity index 76% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs index 1c60c0d9ee..2966097a0f 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs similarity index 77% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs index b4056af443..432789e86e 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs similarity index 74% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs index 26b8712711..e62ee68014 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs similarity index 75% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs index 056ba8a266..8ed22e69b9 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs similarity index 72% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs index 32cf061473..698fa72476 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs similarity index 73% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs index e08e3e9489..86659db6f9 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs similarity index 69% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs index b60f20b7a7..e24c094562 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { [ApiController] public class NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs similarity index 67% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs index abc6b48359..c9b044457f 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { public class NoDiagnosticsAreReturned_ForNonApiController : ControllerBase { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs similarity index 68% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs index 4537050f31..30b7c5cceb 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest { public class Home : PageModel { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs similarity index 72% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs index b3c31d8536..2608c14bc5 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs similarity index 67% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs index 1b5bd73c47..9d10cebb31 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs similarity index 76% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs index 6d1d9db2f5..9a12968723 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs similarity index 70% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs index 8d61173949..8ba42c4bfd 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs similarity index 75% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs index 0a1071c29c..f737c35206 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._INPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs similarity index 70% rename from test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs index 1d82b016e9..a61edaf518 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest._OUTPUT_ { [ApiController] [Route("/api/[controller]")] diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs similarity index 92% rename from test/Mvc.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs index 397c9b86b1..7eb8c8584c 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiConventionAnalyzerTest_IndexModel : PageModel { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs index 2e0fc59064..42ce3bd069 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc; [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs similarity index 92% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs index 7d6169af7e..1b341aed17 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs similarity index 92% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs index 07ea47c1a4..884d613e04 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs @@ -1,11 +1,11 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Analyzers; +using Microsoft.AspNetCore.Mvc.Api.Analyzers; [assembly: ApiConventionType(typeof(DiagnosticsAreReturned_ForControllerWithCustomConvention))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_ForControllerWithCustomConventionController : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 91% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs index ce686e8dcf..3e263cffd9 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 91% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs index eb8f5cd36a..d61842d16f 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -1,7 +1,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs index 1b7dcc3454..f09255b7d0 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs similarity index 94% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs index b27ed2dc61..4a93438cfd 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs @@ -1,6 +1,6 @@ using System.Threading.Tasks; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs similarity index 92% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs index 3de2b09935..1c4a8f59cf 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs similarity index 94% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs index ef4bf9ff03..82d6805a68 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs index fb880c322b..62929c1f2e 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs similarity index 89% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs index fa5b642f2e..a49f31108a 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs @@ -1,9 +1,9 @@ using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Analyzers; +using Microsoft.AspNetCore.Mvc.Api.Analyzers; [assembly: ApiConventionType(typeof(DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCodeConvention))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs similarity index 90% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs index bdbc7bf659..a45428d467 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs similarity index 91% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs index 8893357089..59b1c99c88 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs index 2541e2f98e..ad4c183a51 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs similarity index 85% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs index ad6e5956b4..e2866071d4 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs similarity index 93% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs index 06bb57f1ee..c7aaac3596 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs @@ -2,7 +2,7 @@ [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs similarity index 87% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs index 0e55a8c574..1d790dab91 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class NoDiagnosticsAreReturned_ForNonApiController : Controller { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs similarity index 86% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs index 9299b5b5b8..fa410dc115 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs @@ -1,6 +1,6 @@ using Microsoft.AspNetCore.Mvc.RazorPages; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class Home : PageModel { diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs similarity index 94% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs index bd62df3b3b..0770de818d 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc; [assembly: ApiConventionType(typeof(DefaultApiConventions))] -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class NoDiagnosticsAreReturned_ForReturnStatementsInLambdas : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs similarity index 93% rename from test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs index 332de79235..329bde822d 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [ApiController] public class NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions : ControllerBase diff --git a/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs similarity index 97% rename from test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs index 607715bcb7..085ba88d9a 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs @@ -1,6 +1,6 @@ using System; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public abstract class TestIsControllerActionBase : ControllerBase { diff --git a/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs similarity index 95% rename from test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs index 1837ac712b..1c765c6fd3 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public interface ITestController { } diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs similarity index 97% rename from test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs index 9e549f8016..3d27daef10 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs @@ -1,7 +1,7 @@ using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.ApiExplorer; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class Base { } diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs similarity index 87% rename from test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs index 6d9412a840..8fb75a9e49 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs @@ -2,7 +2,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { [DefaultStatusCode(StatusCodes.Status412PreconditionFailed)] public class TestActionResultUsingStatusCodesConstants { } diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs similarity index 98% rename from test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs index a296f9b93f..9040a07256 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs @@ -5,7 +5,7 @@ using System; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Formatters; -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class GetResponseMetadata_ControllerWithoutConvention : ControllerBase { diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs similarity index 88% rename from test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs index ce2dbbb39b..02a1ffb40a 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult : ControllerBase { diff --git a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs similarity index 81% rename from test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs index 4d5d0d9849..94d66474f5 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs @@ -1,4 +1,4 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult : ControllerBase { From e2b6f077782b91c7aba5c623dc8da0f4b006ddbc Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 1 Aug 2018 16:50:03 -0700 Subject: [PATCH 156/316] Respect LowercaseUrls in ApiExplorer Fixes #8006 --- .../DefaultApiDescriptionProvider.cs | 30 ++++++++++++++++++- .../DefaultApiDescriptionProviderTest.cs | 25 ++++++++++++++-- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index a9d644e2a8..46fa05fb5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -26,6 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer private readonly MvcOptions _mvcOptions; private readonly IActionResultTypeMapper _mapper; private readonly ApiResponseTypeProvider _responseTypeProvider; + private readonly RouteOptions _routeOptions; private readonly IInlineConstraintResolver _constraintResolver; private readonly IModelMetadataProvider _modelMetadataProvider; @@ -53,6 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer /// constraints. /// The . /// The . + [Obsolete("This constructor is obsolete and will be removed in a future release.")] public DefaultApiDescriptionProvider( IOptions optionsAccessor, IInlineConstraintResolver constraintResolver, @@ -66,6 +68,30 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer _responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions); } + /// + /// Creates a new instance of . + /// + /// The accessor for . + /// The used for resolving inline + /// constraints. + /// The . + /// The . + /// The accessor for . + public DefaultApiDescriptionProvider( + IOptions optionsAccessor, + IInlineConstraintResolver constraintResolver, + IModelMetadataProvider modelMetadataProvider, + IActionResultTypeMapper mapper, + IOptions routeOptions) + { + _mvcOptions = optionsAccessor.Value; + _constraintResolver = constraintResolver; + _modelMetadataProvider = modelMetadataProvider; + _mapper = mapper; + _responseTypeProvider = new ApiResponseTypeProvider(modelMetadataProvider, mapper, _mvcOptions); + _routeOptions = routeOptions.Value; + } + /// public int Order => -1000; @@ -383,7 +409,9 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { if (part.IsLiteral) { - currentSegment += part.Text; + currentSegment += _routeOptions.LowercaseUrls ? + part.Text.ToLowerInvariant() : + part.Text; } else if (part.IsParameter) { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 5caceeaa02..224a6b1276 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -387,6 +387,25 @@ namespace Microsoft.AspNetCore.Mvc.Description Assert.Single(description.ParameterDescriptions, p => p.Name == "id5"); } + [Fact] + public void GetApiDescription_ProducesLowerCaseRelativePaths() + { + // Arrange + var action = CreateActionDescriptor(); + action.AttributeRouteInfo = new AttributeRouteInfo + { + Template = "api/Products/UpdateProduct/{productId}" + }; + var routeOptions = new RouteOptions { LowercaseUrls = true }; + + // Act + var descriptions = GetApiDescriptions(action, routeOptions: routeOptions); + + // Assert + var description = Assert.Single(descriptions); + Assert.Equal("api/products/updateproduct/{productId}", description.RelativePath); + } + [Fact] public void GetApiDescription_PopulatesResponseType_WithProduct() { @@ -1797,7 +1816,8 @@ namespace Microsoft.AspNetCore.Mvc.Description ActionDescriptor action, List inputFormatters = null, List outputFormatters = null, - bool allowValidatingTopLevelNodes = true) + bool allowValidatingTopLevelNodes = true, + RouteOptions routeOptions = null) { var context = new ApiDescriptionProviderContext(new ActionDescriptor[] { action }); @@ -1827,7 +1847,8 @@ namespace Microsoft.AspNetCore.Mvc.Description optionsAccessor, constraintResolver.Object, modelMetadataProvider, - new ActionResultTypeMapper()); + new ActionResultTypeMapper(), + Options.Create(routeOptions ?? new RouteOptions())); provider.OnProvidersExecuting(context); provider.OnProvidersExecuted(context); From 0989231ed58706676e7780071dfd5a1c76c51eea Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 2 Aug 2018 15:09:48 -0700 Subject: [PATCH 157/316] Make structs readonly --- .../ActionSelectorCandidate.cs | 2 +- .../ModelBinding/EnumGroupAndName.cs | 2 +- .../Metadata/ModelMetadataIdentity.cs | 41 +++++++++---------- .../ModelBinding/ModelBindingContext.cs | 2 +- .../ModelBinding/ModelBindingResult.cs | 2 +- .../ModelBinding/ModelStateDictionary.cs | 6 +-- .../ModelBinding/ValueProviderResult.cs | 2 +- ...ApiActionsAreAttributeRoutedFixProvider.cs | 2 +- .../DefaultApiDescriptionProvider.cs | 2 +- .../Formatters/MediaType.cs | 4 +- .../Internal/ClientValidatorCache.cs | 2 +- .../ControllerBinderDelegateProvider.cs | 2 +- .../Internal/FilterCursorItem.cs | 2 +- .../Internal/FilterFactoryResult.cs | 2 +- .../Internal/MediaTypeSegmentWithQuality.cs | 2 +- .../Internal/ValidatorCache.cs | 2 +- .../Metadata/DefaultModelMetadataProvider.cs | 2 +- .../ModelBinding/ModelBinderFactory.cs | 2 +- .../Validation/ValidationVisitor.cs | 2 +- .../Internal/ViewLocationCacheItem.cs | 2 +- .../Internal/ViewLocationCacheKey.cs | 2 +- .../RazorPageFactoryResult.cs | 2 +- .../RazorPageResult.cs | 2 +- .../Internal/PageBinderFactory.cs | 2 +- .../Internal/GlobbingUrlBuilder.cs | 2 +- .../Internal/ViewBufferValue.cs | 8 ++-- .../ViewFeatures/MemberExpressionCacheKey.cs | 6 +-- 27 files changed, 53 insertions(+), 56 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs index c2251cd371..7cf58a4845 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.ActionConstraints /// /// A candidate action for action selection. /// - public struct ActionSelectorCandidate + public readonly struct ActionSelectorCandidate { /// /// Creates a new . diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs index 0ad0117e5d..d4a141ed00 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// An abstraction used when grouping enum values for . /// - public struct EnumGroupAndName + public readonly struct EnumGroupAndName { private readonly Func _name; diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs index af97d9a043..655bd9cb74 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs @@ -11,8 +11,20 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// /// A key type which identifies a . /// - public struct ModelMetadataIdentity : IEquatable + public readonly struct ModelMetadataIdentity : IEquatable { + private ModelMetadataIdentity( + Type modelType, + string name = null, + Type containerType = null, + ParameterInfo parameterInfo = null) + { + ModelType = modelType; + Name = name; + ContainerType = containerType; + ParameterInfo = parameterInfo; + } + /// /// Creates a for the provided model . /// @@ -25,10 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata throw new ArgumentNullException(nameof(modelType)); } - return new ModelMetadataIdentity() - { - ModelType = modelType, - }; + return new ModelMetadataIdentity(modelType); } /// @@ -58,12 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(name)); } - return new ModelMetadataIdentity() - { - ModelType = modelType, - Name = name, - ContainerType = containerType, - }; + return new ModelMetadataIdentity(modelType, name, containerType); } /// @@ -93,24 +97,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata throw new ArgumentNullException(nameof(modelType)); } - return new ModelMetadataIdentity() - { - Name = parameter.Name, - ModelType = modelType, - ParameterInfo = parameter, - }; + return new ModelMetadataIdentity(modelType, parameter.Name, parameterInfo: parameter); } /// /// Gets the defining the model property represented by the current /// instance, or null if the current instance does not represent a property. /// - public Type ContainerType { get; private set; } + public Type ContainerType { get; } /// /// Gets the represented by the current instance. /// - public Type ModelType { get; private set; } + public Type ModelType { get; } /// /// Gets a value indicating the kind of metadata represented by the current instance. @@ -138,13 +137,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// Gets the name of the current instance if it represents a parameter or property, or null if /// the current instance represents a type. /// - public string Name { get; private set; } + public string Name { get; } /// /// Gets a descriptor for the parameter, or null if this instance /// does not represent a parameter. /// - public ParameterInfo ParameterInfo { get; private set; } + public ParameterInfo ParameterInfo { get; } /// public bool Equals(ModelMetadataIdentity other) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs index 50c0149cc2..e133b6816e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs @@ -156,7 +156,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// by caller when child binding context state should be popped off of /// the . /// - public struct NestedScope : IDisposable + public readonly struct NestedScope : IDisposable { private readonly ModelBindingContext _context; diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs index de57b8bf88..2bfc3dd5fd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// /// Contains the result of model binding. /// - public struct ModelBindingResult : IEquatable + public readonly struct ModelBindingResult : IEquatable { /// /// Creates a representing a failed model binding operation. diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs index 970e5b6975..68781961e6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs @@ -1003,7 +1003,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } } - public struct PrefixEnumerable : IEnumerable> + public readonly struct PrefixEnumerable : IEnumerable> { private readonly ModelStateDictionary _dictionary; private readonly string _prefix; @@ -1136,7 +1136,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } } - public struct KeyEnumerable : IEnumerable + public readonly struct KeyEnumerable : IEnumerable { private readonly ModelStateDictionary _dictionary; @@ -1191,7 +1191,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } } - public struct ValueEnumerable : IEnumerable + public readonly struct ValueEnumerable : IEnumerable { private readonly ModelStateDictionary _dictionary; diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs index c9bf19ad4c..3a0b8e1fa8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// regardless of whether a single value or multiple values were submitted. /// /// - public struct ValueProviderResult : IEquatable, IEnumerable + public readonly struct ValueProviderResult : IEquatable, IEnumerable { private static readonly CultureInfo _invariantCulture = CultureInfo.InvariantCulture; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs index a0ea810b51..f89827b8f0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs @@ -168,7 +168,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } - private struct RouteAttributeInfo + private readonly struct RouteAttributeInfo { public RouteAttributeInfo(string name, string type, string[] keywords) { diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs index 46fa05fb5a..6fd8c7e65f 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs @@ -659,7 +659,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } } - private struct PropertyKey + private readonly struct PropertyKey { public readonly Type ContainerType; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs index 00e2459f09..7c557ed4cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters /// /// A media type value. /// - public struct MediaType + public readonly struct MediaType { private static readonly StringSegment QualityParameter = new StringSegment("q"); @@ -683,7 +683,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters } } - private struct MediaTypeParameter : IEquatable + private readonly struct MediaTypeParameter : IEquatable { public MediaTypeParameter(StringSegment name, StringSegment value) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs index 37adde40c2..0bcc97f67b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return validators; } - private struct CacheEntry + private readonly struct CacheEntry { public CacheEntry(IReadOnlyList validators) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs index e29525ea11..20d600281e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs @@ -194,7 +194,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return propertyBindingInfo; } - private struct BinderItem + private readonly struct BinderItem { public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs index ccb42dc3f7..a62aa1b8cc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs @@ -3,7 +3,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { - public struct FilterCursorItem + public readonly struct FilterCursorItem { public FilterCursorItem(TFilter filter, TFilterAsync filterAsync) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs index e532a08427..69e39cfea9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc.Filters; namespace Microsoft.AspNetCore.Mvc.Internal { - public struct FilterFactoryResult + public readonly struct FilterFactoryResult { public FilterFactoryResult( FilterItem[] cacheableFilters, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs index c9a8f4502e..52d6281ed1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Internal /// /// A media type with its associated quality. /// - public struct MediaTypeSegmentWithQuality + public readonly struct MediaTypeSegmentWithQuality { /// /// Initializes an instance of . diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs index 61fcfe0c15..61e8303d40 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs @@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return validators; } - private struct CacheEntry + private readonly struct CacheEntry { public CacheEntry(IReadOnlyList validators) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs index b9e374701e..75bd188997 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -345,7 +345,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata { } - private struct ModelMetadataCacheEntry + private readonly struct ModelMetadataCacheEntry { public ModelMetadataCacheEntry(ModelMetadata metadata, DefaultMetadataDetails details) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs index 08e6883dd8..4b7a53b498 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs @@ -310,7 +310,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // the ParameterDescriptor) or in a call to TryUpdateModel (no BindingInfo) or as a collection element. // // We need to be able to tell the difference between these things to avoid over-caching. - private struct Key : IEquatable + private readonly struct Key : IEquatable { private readonly ModelMetadata _metadata; private readonly object _token; // Explicitly using ReferenceEquality for tokens. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index 43c385ec7b..fa467588a8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -318,7 +318,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation return entry; } - protected struct StateManager : IDisposable + protected readonly struct StateManager : IDisposable { private readonly ValidationVisitor _visitor; private readonly object _container; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs index 23fe788101..64333eb02f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs @@ -8,7 +8,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal /// /// An item in . /// - public struct ViewLocationCacheItem + public readonly struct ViewLocationCacheItem { /// /// Initializes a new instance of . diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs index 7643882f79..00f3d3c8ec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal /// /// Key for entries in . /// - public struct ViewLocationCacheKey : IEquatable + public readonly struct ViewLocationCacheKey : IEquatable { /// /// Initializes a new instance of . diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs index 31f7b1ce94..45537550f7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// /// Result of . /// - public struct RazorPageFactoryResult + public readonly struct RazorPageFactoryResult { /// /// Initializes a new instance of with the diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs index c299e350fe..044e3ee581 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// /// Result of locating a . /// - public struct RazorPageResult + public readonly struct RazorPageResult { /// /// Initializes a new instance of for a successful discovery. diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs index 6fcc9e08c8..72fd33d754 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } } - private struct BinderItem + private readonly struct BinderItem { public BinderItem(IModelBinder modelBinder, ModelMetadata modelMetadata) { diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs index 9b756848a1..ce891ad84e 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs @@ -352,7 +352,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal return new StringSegment(value.Buffer, offset, trimmedEnd - offset + 1); } - private struct GlobbingUrlKey : IEquatable + private readonly struct GlobbingUrlKey : IEquatable { public GlobbingUrlKey(string include, string exclude) { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs index 89c9581848..68691a9868 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal /// Encapsulates a string or value. /// [DebuggerDisplay("{DebuggerToString()}")] - public struct ViewBufferValue + public readonly struct ViewBufferValue { /// /// Initializes a new instance of with a string value. @@ -41,15 +41,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { using (var writer = new StringWriter()) { - var valueAsString = Value as string; - if (valueAsString != null) + if (Value is string valueAsString) { writer.Write(valueAsString); return writer.ToString(); } - var valueAsContent = Value as IHtmlContent; - if (valueAsContent != null) + if (Value is IHtmlContent valueAsContent) { valueAsContent.WriteTo(writer, HtmlEncoder.Default); return writer.ToString(); diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs index 1cbc80c38a..8f8b667983 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs @@ -8,7 +8,7 @@ using System.Reflection; namespace Microsoft.AspNetCore.Mvc.ViewFeatures { - internal struct MemberExpressionCacheKey + internal readonly struct MemberExpressionCacheKey { public MemberExpressionCacheKey(Type modelType, MemberExpression memberExpression) { @@ -44,7 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public MemberInfo[] Members { get; } - public Enumerator GetEnumerator() => new Enumerator(ref this); + public Enumerator GetEnumerator() => new Enumerator(this); public struct Enumerator { @@ -52,7 +52,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures private int _index; private MemberExpression _memberExpression; - public Enumerator(ref MemberExpressionCacheKey key) + public Enumerator(in MemberExpressionCacheKey key) { Current = null; _members = key.Members; From ce8fc29728394ddf104bdfc1bf164a7895e5f7ae Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 2 Aug 2018 15:36:27 -0700 Subject: [PATCH 158/316] ConsumesAttribute accepts requests without content type Fixes #8174 --- .../ConsumesAttribute.cs | 4 +--- .../ConsumesAttributeTests.cs | 5 +++-- .../ConsumesAttributeEndpointRoutingTests.cs | 18 ------------------ .../ConsumesAttributeTestsBase.cs | 8 ++------ 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs index 8a6efd7362..7af167dc51 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -10,8 +10,6 @@ using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Routing; using Microsoft.Net.Http.Headers; using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; @@ -79,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc // Confirm the request's content type is more specific than a media type this action supports e.g. OK // if client sent "text/plain" data and this action supports "text/*". - if (requestContentType != null && !IsSubsetOfAnyContentType(requestContentType)) + if (requestContentType == null || !IsSubsetOfAnyContentType(requestContentType)) { context.Result = new UnsupportedMediaTypeResult(); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs index 57a0bbddf1..be1999d28d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs @@ -326,7 +326,7 @@ namespace Microsoft.AspNetCore.Mvc [Theory] [InlineData("")] [InlineData(null)] - public void OnResourceExecuting_NullOrEmptyRequestContentType_IsNoOp(string contentType) + public void OnResourceExecuting_NullOrEmptyRequestContentType_SetsUnsupportedMediaTypeResult(string contentType) { // Arrange var httpContext = new DefaultHttpContext(); @@ -349,7 +349,8 @@ namespace Microsoft.AspNetCore.Mvc consumesFilter.OnResourceExecuting(resourceExecutingContext); // Assert - Assert.Null(resourceExecutingContext.Result); + Assert.NotNull(resourceExecutingContext.Result); + Assert.IsType(resourceExecutingContext.Result); } [Theory] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs index 9aebbcb2d4..7377442ac6 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; -using System.Net.Http; using System.Threading.Tasks; using Newtonsoft.Json; using Xunit; @@ -30,22 +29,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.True(result); } - - // The endpoint routing version of this feature has fixed https://github.com/aspnet/Mvc/issues/8174 - [Fact] - public override async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() - { - // Arrange - var request = new HttpRequestMessage( - HttpMethod.Post, - "http://localhost/ConsumesAttribute_PassThrough/CreateProduct"); - - // Act - var response = await Client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - - // Assert - Assert.Equal(HttpStatusCode.UnsupportedMediaType, response.StatusCode); - } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index 302d50fc9d..fca64c9dc4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public virtual async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent() + public async Task NoRequestContentType_Selects_IfASingleActionWithConstraintIsPresent_ReturnsUnsupported() { // Arrange var request = new HttpRequestMessage( @@ -58,11 +58,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Act var response = await Client.SendAsync(request); - var body = await response.Content.ReadAsStringAsync(); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Equal("ConsumesAttribute_PassThrough_Product_Json", body); + await response.AssertStatusCodeAsync(HttpStatusCode.UnsupportedMediaType); } [Theory] From 90b093baac9caa52552e7a09156a0f5c4b9add3b Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Thu, 2 Aug 2018 16:59:57 -0700 Subject: [PATCH 159/316] Updated dependencies.props --- build/dependencies.props | 146 +++++++++++++++++++-------------------- 1 file changed, 73 insertions(+), 73 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 9ab9fc484e..6697d0589f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 2.2.0-preview1-17102 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-a-preview1-routing-api-review-p1-16821 - 2.2.0-a-preview1-routing-api-review-p1-16821 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 1.7.0 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 2.1.0 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34823 - 2.2.0-preview1-34823 + 2.2.0-preview1-34869 + 2.2.0-preview1-34869 15.6.1 4.7.49 2.0.3 From ac410b76d9e1a3a454bf21b79491ee6d9d0e15b6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 3 Aug 2018 16:30:57 +1200 Subject: [PATCH 160/316] Change MvcEndpointInfo to internal (#8210) --- .../MvcApplicationBuilderExtensions.cs | 32 -- .../Builder/MvcEndpointInfo.cs | 2 +- .../Builder/MvcEndpointInfoBuilder.cs | 19 -- .../MvcEndpointInfoBuilderExtensions.cs | 229 -------------- .../Internal/MvcEndpointDataSource.cs | 1 - .../MvcApplicationBuilderExtensionsTest.cs | 20 -- .../MvcEndpointInfoBuilderExtensionsTest.cs | 282 ------------------ 7 files changed, 1 insertion(+), 584 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index ab4b465492..08baf62c2e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -171,38 +171,6 @@ namespace Microsoft.AspNetCore.Builder } } - public static IApplicationBuilder UseMvcWithEndpoint( - this IApplicationBuilder app, - Action configureRoutes) - { - if (app == null) - { - throw new ArgumentNullException(nameof(app)); - } - - if (configureRoutes == null) - { - throw new ArgumentNullException(nameof(configureRoutes)); - } - - VerifyMvcIsRegistered(app); - - var mvcEndpointDataSource = app.ApplicationServices - .GetRequiredService>() - .OfType() - .First(); - - var constraintResolver = app.ApplicationServices.GetRequiredService(); - - MvcEndpointInfoBuilder routeBuilder = new MvcEndpointInfoBuilder(constraintResolver); - - configureRoutes(routeBuilder); - - mvcEndpointDataSource.ConventionalEndpointInfos.AddRange(routeBuilder.EndpointInfos); - - return app.UseEndpoint(); - } - private static void VerifyMvcIsRegistered(IApplicationBuilder app) { // Verify if AddMvc was done before calling UseMvc diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index ba59c34b44..e0943b82c0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Routing.Template; namespace Microsoft.AspNetCore.Builder { - public class MvcEndpointInfo + internal class MvcEndpointInfo { public MvcEndpointInfo( string name, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs deleted file mode 100644 index 99000fdd08..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilder.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Microsoft.AspNetCore.Routing; - -namespace Microsoft.AspNetCore.Builder -{ - public class MvcEndpointInfoBuilder - { - public MvcEndpointInfoBuilder(IInlineConstraintResolver constraintResolver) - { - ConstraintResolver = constraintResolver; - } - - public List EndpointInfos { get; } = new List(); - public IInlineConstraintResolver ConstraintResolver { get; } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs deleted file mode 100644 index b52ef3692b..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfoBuilderExtensions.cs +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Constraints; - -namespace Microsoft.AspNetCore.Builder -{ - /// - /// Provides extension methods for to add endpoints. - /// - public static class MvcEndpointInfoBuilderExtensions - { - #region MapEndpoint - /// - /// Adds a endpoint to the with the specified name and template. - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The URL pattern of the endpoint. - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template) - { - endpointBuilder.MapEndpoint(name, template, null); - return endpointBuilder; - } - - /// - /// Adds a endpoint to the with the specified name, template, and default values. - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the names - /// and values of the default values. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults) - { - return endpointBuilder.MapEndpoint(name, template, defaults, null); - } - - /// - /// Adds a endpoint to the with the specified name, template, default values, and - /// constraints. - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the names - /// and values of the default values. - /// - /// - /// An object that contains constraints for the endpoint. The object's properties represent the names and values - /// of the constraints. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints) - { - return endpointBuilder.MapEndpoint(name, template, defaults, constraints, null); - } - - /// - /// Adds a endpoint to the with the specified name, template, default values, and - /// data tokens. - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the names - /// and values of the default values. - /// - /// - /// An object that contains constraints for the endpoint. The object's properties represent the names and values - /// of the constraints. - /// - /// - /// An object that contains data tokens for the endpoint. The object's properties represent the names and values - /// of the data tokens. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapEndpoint(this MvcEndpointInfoBuilder endpointBuilder, string name, string template, object defaults, object constraints, object dataTokens) - { - endpointBuilder.EndpointInfos.Add(new MvcEndpointInfo( - name, - template, - new RouteValueDictionary(defaults), - new RouteValueDictionary(constraints), - new RouteValueDictionary(dataTokens), - endpointBuilder.ConstraintResolver)); - - return endpointBuilder; - } - #endregion - - #region MapAreaEndpoint - /// - /// Adds a endpoint to the with the given MVC area with the specified - /// , and . - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The MVC area name. - /// The URL pattern of the endpoint. - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapAreaEndpoint( - this MvcEndpointInfoBuilder endpointBuilder, - string name, - string areaName, - string template) - { - MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults: null, constraints: null, dataTokens: null); - return endpointBuilder; - } - - /// - /// Adds a endpoint to the with the given MVC area with the specified - /// , , , and - /// . - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The MVC area name. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the - /// names and values of the default values. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapAreaEndpoint( - this MvcEndpointInfoBuilder endpointBuilder, - string name, - string areaName, - string template, - object defaults) - { - MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints: null, dataTokens: null); - return endpointBuilder; - } - - /// - /// Adds a endpoint to the with the given MVC area with the specified - /// , , , - /// , and . - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The MVC area name. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the - /// names and values of the default values. - /// - /// - /// An object that contains constraints for the endpoint. The object's properties represent the names and - /// values of the constraints. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapAreaEndpoint( - this MvcEndpointInfoBuilder endpointBuilder, - string name, - string areaName, - string template, - object defaults, - object constraints) - { - MapAreaEndpoint(endpointBuilder, name, areaName, template, defaults, constraints, dataTokens: null); - return endpointBuilder; - } - - /// - /// Adds a endpoint to the with the given MVC area with the specified - /// , , , - /// , , and . - /// - /// The to add the endpoint to. - /// The name of the endpoint. - /// The MVC area name. - /// The URL pattern of the endpoint. - /// - /// An object that contains default values for endpoint parameters. The object's properties represent the - /// names and values of the default values. - /// - /// - /// An object that contains constraints for the endpoint. The object's properties represent the names and - /// values of the constraints. - /// - /// - /// An object that contains data tokens for the endpoint. The object's properties represent the names and - /// values of the data tokens. - /// - /// A reference to this instance after the operation has completed. - public static MvcEndpointInfoBuilder MapAreaEndpoint( - this MvcEndpointInfoBuilder endpointBuilder, - string name, - string areaName, - string template, - object defaults, - object constraints, - object dataTokens) - { - if (endpointBuilder == null) - { - throw new ArgumentNullException(nameof(endpointBuilder)); - } - - if (string.IsNullOrEmpty(areaName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(areaName)); - } - - var defaultsDictionary = new RouteValueDictionary(defaults); - defaultsDictionary["area"] = defaultsDictionary["area"] ?? areaName; - - var constraintsDictionary = new RouteValueDictionary(constraints); - constraintsDictionary["area"] = constraintsDictionary["area"] ?? new StringRouteConstraint(areaName); - - endpointBuilder.MapEndpoint(name, template, defaultsDictionary, constraintsDictionary, dataTokens); - return endpointBuilder; - } - #endregion - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 180b34011f..3770856e64 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -442,7 +442,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - // REVIEW: Infos added after endpoints are initialized will not be used public List ConventionalEndpointInfos { get; } private class RouteNameMetadata : IRouteNameMetadata diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index 39fd3847db..d731a697f2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -37,26 +37,6 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder exception.Message); } - [Fact] - public void UseMvcWithEndpoint_ThrowsInvalidOperationException_IfMvcMarkerServiceIsNotRegistered() - { - // Arrange - var applicationBuilderMock = new Mock(); - applicationBuilderMock - .Setup(s => s.ApplicationServices) - .Returns(Mock.Of()); - - // Act & Assert - var exception = Assert.Throws( - () => applicationBuilderMock.Object.UseMvcWithEndpoint(rb => { })); - - Assert.Equal( - "Unable to find the required services. Please add all the required services by calling " + - "'IServiceCollection.AddMvc' inside the call to 'ConfigureServices(...)' " + - "in the application startup code.", - exception.Message); - } - [Fact] public void UseMvc_EndpointRoutingDisabled_NoEndpointInfos() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs deleted file mode 100644 index 4a1431793b..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcEndpointInfoBuilderExtensionsTest.cs +++ /dev/null @@ -1,282 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Constraints; -using Moq; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Core.Test.Builder -{ - public class MvcEndpointInfoBuilderExtensionsTest - { - #region MapAreaEndpoint - [Fact] - public void MapAreaEndpoint_Simple() - { - // Arrange - var builder = CreateEndpointBuilder(); - - // Act - builder.MapAreaEndpoint(name: null, areaName: "admin", template: "site/Admin/"); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Null(endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - }); - Assert.Empty(endpointInfo.DataTokens); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("admin", kvp.Value); - }); - } - - [Fact] - public void MapAreaEndpoint_Defaults() - { - // Arrange - var builder = CreateEndpointBuilder(); - - // Act - builder.MapAreaEndpoint( - name: "admin_area", - areaName: "admin", - template: "site/Admin/", - defaults: new { action = "Home" }); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Equal("admin_area", endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - }); - Assert.Empty(endpointInfo.DataTokens); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Home", kvp.Value); - }, - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("admin", kvp.Value); - }); - } - - [Fact] - public void MapAreaEndpoint_DefaultsAndConstraints() - { - // Arrange - var builder = CreateEndpointBuilder(); - - // Act - builder.MapAreaEndpoint( - name: "admin_area", - areaName: "admin", - template: "site/Admin/", - defaults: new { action = "Home" }, - constraints: new { id = new IntRouteConstraint() }); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Equal("admin_area", endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - }, - kvp => - { - Assert.Equal("id", kvp.Key); - Assert.IsType(kvp.Value); - }); - Assert.Empty(endpointInfo.DataTokens); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Home", kvp.Value); - }, - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("admin", kvp.Value); - }); - } - - [Fact] - public void MapAreaEndpoint_DefaultsConstraintsAndDataTokens() - { - // Arrange - var builder = CreateEndpointBuilder(); - - // Act - builder.MapAreaEndpoint( - name: "admin_area", - areaName: "admin", - template: "site/Admin/", - defaults: new { action = "Home" }, - constraints: new { id = new IntRouteConstraint() }, - dataTokens: new { some_token = "hello" }); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Equal("admin_area", endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - }, - kvp => - { - Assert.Equal("id", kvp.Key); - Assert.IsType(kvp.Value); - }); - Assert.Collection( - endpointInfo.DataTokens.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("some_token", kvp.Key); - Assert.Equal("hello", kvp.Value); - }); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("action", kvp.Key); - Assert.Equal("Home", kvp.Value); - }, - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("admin", kvp.Value); - }); - } - - [Fact] - public void MapAreaEndpoint_DoesNotReplaceValuesForAreaIfAlreadyPresentInConstraintsOrDefaults() - { - // Arrange - var builder = CreateEndpointBuilder(); - - // Act - builder.MapAreaEndpoint( - name: "admin_area", - areaName: "admin", - template: "site/Admin/", - defaults: new { area = "Home" }, - constraints: new { area = new IntRouteConstraint() }, - dataTokens: new { some_token = "hello" }); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Equal("admin_area", endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - }); - Assert.Collection( - endpointInfo.DataTokens.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("some_token", kvp.Key); - Assert.Equal("hello", kvp.Value); - }); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal("Home", kvp.Value); - }); - } - - [Fact] - public void MapAreaEndpoint_UsesPassedInAreaNameAsIs() - { - // Arrange - var builder = CreateEndpointBuilder(); - var areaName = "user.admin"; - - // Act - builder.MapAreaEndpoint(name: null, areaName: areaName, template: "site/Admin/"); - - // Assert - var endpointInfo = Assert.Single(builder.EndpointInfos); - - Assert.Null(endpointInfo.Name); - Assert.Equal("site/Admin/", endpointInfo.Template); - Assert.Collection( - endpointInfo.Constraints.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.IsType(kvp.Value); - - var values = new RouteValueDictionary(new { area = areaName }); - var match = kvp.Value.Match( - new DefaultHttpContext(), - route: new Mock().Object, - routeKey: kvp.Key, - values: values, - routeDirection: RouteDirection.UrlGeneration); - - Assert.True(match); - }); - Assert.Empty(endpointInfo.DataTokens); - Assert.Collection( - endpointInfo.Defaults.OrderBy(kvp => kvp.Key), - kvp => - { - Assert.Equal("area", kvp.Key); - Assert.Equal(kvp.Value, areaName); - }); - } - #endregion - - private MvcEndpointInfoBuilder CreateEndpointBuilder() - { - var builder = new MvcEndpointInfoBuilder(Mock.Of()); - return builder; - } - } -} \ No newline at end of file From a375cba3590074c5182b50a71ad21b61a4b882df Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 3 Aug 2018 12:04:44 -0700 Subject: [PATCH 161/316] Copy action constraints and EndPointMetadata when setting up a PageActionDescriptor (#8208) * Copy action constraints and EndPointMetadata when setting up a PageActionDescriptor Fixes #8207 --- .../PageActionDescriptorProvider.cs | 4 +- .../CompiledPageActionDescriptorBuilder.cs | 1 + .../PageActionDescriptorProviderTest.cs | 69 +++++++++++++++++++ ...CompiledPageActionDescriptorBuilderTest.cs | 2 + 4 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs index 7e1dbb47c2..06bc6f52eb 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs @@ -75,6 +75,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { var descriptor = new PageActionDescriptor { + ActionConstraints = selector.ActionConstraints.ToList(), + AreaName = model.AreaName, AttributeRouteInfo = new AttributeRouteInfo { Name = selector.AttributeRouteModel.Name, @@ -84,11 +86,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching, }, DisplayName = $"Page: {model.ViewEnginePath}", + EndpointMetadata = selector.EndpointMetadata.ToList(), FilterDescriptors = Array.Empty(), Properties = new Dictionary(model.Properties), RelativePath = model.RelativePath, ViewEnginePath = model.ViewEnginePath, - AreaName = model.AreaName, }; foreach (var kvp in model.RouteValues) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs index 29f7214448..41055bf878 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs @@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal ActionConstraints = actionDescriptor.ActionConstraints, AttributeRouteInfo = actionDescriptor.AttributeRouteInfo, BoundProperties = boundProperties, + EndpointMetadata = actionDescriptor.EndpointMetadata, FilterDescriptors = filters, HandlerMethods = handlerMethods, HandlerTypeInfo = applicationModel.HandlerType, diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs index 095a5f5415..4b1e2c4429 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs @@ -4,6 +4,7 @@ using System; using System.Linq; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.Extensions.Options; using Moq; @@ -167,6 +168,74 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure Assert.Equal("Accounts/Test/{id:int?}", descriptor.AttributeRouteInfo.Template); } + [Fact] + public void GetDescriptors_CopiesActionConstraintsFromModel() + { + // Arrange + var expected = Mock.Of(); + var model = new PageRouteModel("/Areas/Accounts/Pages/Test.cshtml", "/Test", "Accounts") + { + Selectors = + { + new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel(), + ActionConstraints = { expected } + } + }, + }; + var applicationModelProvider = new TestPageRouteModelProvider(model); + var provider = new PageActionDescriptorProvider( + new[] { applicationModelProvider }, + GetAccessor(), + GetRazorPagesOptions()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var result = Assert.Single(context.Results); + var descriptor = Assert.IsType(result); + Assert.Equal(model.RelativePath, descriptor.RelativePath); + var actual = Assert.Single(descriptor.ActionConstraints); + Assert.Same(expected, actual); + } + + [Fact] + public void GetDescriptors_CopiesEndPointMetadataFromModel() + { + // Arrange + var expected = new object(); + var model = new PageRouteModel("/Test.cshtml", "/Test", "Accounts") + { + Selectors = + { + new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel(), + EndpointMetadata = { expected } + } + }, + }; + var applicationModelProvider = new TestPageRouteModelProvider(model); + var provider = new PageActionDescriptorProvider( + new[] { applicationModelProvider }, + GetAccessor(), + GetRazorPagesOptions()); + var context = new ActionDescriptorProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var result = Assert.Single(context.Results); + var descriptor = Assert.IsType(result); + Assert.Equal(model.RelativePath, descriptor.RelativePath); + var actual = Assert.Single(descriptor.EndpointMetadata); + Assert.Same(expected, actual); + } + [Fact] public void GetDescriptors_AddsActionDescriptorForEachSelector() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs index 2d08aee3d8..7d12648d04 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs @@ -25,6 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { ActionConstraints = new List(), AttributeRouteInfo = new AttributeRouteInfo(), + EndpointMetadata = new List(), FilterDescriptors = new List(), RelativePath = "/Foo", RouteValues = new Dictionary(), @@ -40,6 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Same(actionDescriptor.ActionConstraints, actual.ActionConstraints); Assert.Same(actionDescriptor.AttributeRouteInfo, actual.AttributeRouteInfo); + Assert.Same(actionDescriptor.EndpointMetadata, actual.EndpointMetadata); Assert.Same(actionDescriptor.RelativePath, actual.RelativePath); Assert.Same(actionDescriptor.RouteValues, actual.RouteValues); Assert.Same(actionDescriptor.ViewEnginePath, actual.ViewEnginePath); From 6efb51d8179ae6981300aaadd83fb5c8e45e6eb0 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 3 Aug 2018 14:46:45 -0700 Subject: [PATCH 162/316] Add Microsoft.AspNetCore.Mvc.Api.Analyzers to Mvc.sln --- Mvc.sln | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/Mvc.sln b/Mvc.sln index 8223801973..7478c7b22e 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -178,6 +178,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -938,6 +940,18 @@ Global {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|Mixed Platforms.Build.0 = Release|Any CPU {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|x86.ActiveCfg = Release|Any CPU {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}.Release|x86.Build.0 = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|x86.ActiveCfg = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Debug|x86.Build.0 = Debug|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Any CPU.Build.0 = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.ActiveCfg = Release|Any CPU + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1010,6 +1024,7 @@ Global {910F023A-88E3-4CB4-8793-AC4005C7B421} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} From d4472f08ed210d57ffafdf1c1df0b5cbcc8410b7 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 4 Aug 2018 09:58:00 +1200 Subject: [PATCH 163/316] Remove legacy EnableGlobalRouting option (#8214) --- src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index b3fa3e3745..eb80659155 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -68,13 +68,6 @@ namespace Microsoft.AspNetCore.Mvc }; } - // REVIEW: Remove once web hooks is using EnableEndpointRouting - public bool EnableGlobalRouting - { - get => EnableEndpointRouting; - set => EnableEndpointRouting = value; - } - // REVIEW: Add documentation public bool EnableEndpointRouting { From c16f86f0ef3781e6c86ca9677a3aa8da2266348a Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Fri, 3 Aug 2018 13:18:32 -0700 Subject: [PATCH 164/316] React to Routing repo's api changes --- build/dependencies.props | 6 +-- .../ControllerActionDescriptorBuilder.cs | 1 - .../DefaultApplicationModelProvider.cs | 2 +- .../Internal/MvcEndpointDataSource.cs | 30 +++++--------- .../Routing/ConsumesMatcherPolicy.cs | 1 - .../Routing/EndpointRoutingUrlHelper.cs | 41 +++---------------- .../Routing/UrlHelperFactory.cs | 2 - .../Internal/CorsApplicationModelProvider.cs | 2 +- ...ControllerActionDescriptorProviderTests.cs | 2 +- .../Internal/MvcEndpointDataSourceTests.cs | 23 ++++++----- .../ActionConstraintMatcherPolicyTest.cs | 5 --- .../Routing/ConsumesMatcherPolicyTest.cs | 1 - .../Routing/EndpointRoutingUrlHelperTest.cs | 33 ++++++--------- .../CorsApplicationModelProviderTest.cs | 2 +- 14 files changed, 48 insertions(+), 103 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 6697d0589f..3015d4e3c2 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -39,7 +39,7 @@ 2.2.0-preview1-34869 2.2.0-preview1-34869 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-a-preview1-routing-lg-16736 2.2.0-preview1-34869 2.2.0-preview1-34869 2.2.0-preview1-34869 @@ -48,8 +48,8 @@ 2.2.0-preview1-34869 2.2.0-preview1-34869 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-a-preview1-change-linkgenerator-api-16845 + 2.2.0-a-preview1-change-linkgenerator-api-16845 2.2.0-preview1-34869 2.2.0-preview1-34869 2.2.0-preview1-34869 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index f24859eb20..27a548c37a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -12,7 +12,6 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Routing.Metadata; using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs index 00ad6bf594..c79b2b209d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs @@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Routing.Metadata; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 3770856e64..9e4ff9b92c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -3,17 +3,14 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Metadata; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; @@ -295,11 +292,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal var defaults = new RouteValueDictionary(nonInlineDefaults); EnsureRequiredValuesInDefaults(action.RouteValues, defaults); - var metadataCollection = BuildEndpointMetadata(action, routeName, source, suppressLinkGeneration); + var metadataCollection = BuildEndpointMetadata( + action, + routeName, + new RouteValueDictionary(action.RouteValues), + source, + suppressLinkGeneration); + var endpoint = new MatcherEndpoint( next => invokerDelegate, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(action.RouteValues), order, metadataCollection, action.DisplayName); @@ -310,6 +312,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static EndpointMetadataCollection BuildEndpointMetadata( ActionDescriptor action, string routeName, + RouteValueDictionary requiredValues, object source, bool suppressLinkGeneration) { @@ -323,10 +326,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.AddRange(action.EndpointMetadata); } - if (!string.IsNullOrEmpty(routeName)) - { - metadata.Add(new RouteNameMetadata(routeName)); - } + metadata.Add(new RouteValuesAddressMetadata(routeName, requiredValues)); // Add filter descriptors to endpoint metadata if (action.FilterDescriptors != null && action.FilterDescriptors.Count > 0) @@ -349,7 +349,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { metadata.Add(new HttpMethodMetadata(httpMethodActionConstraint.HttpMethods)); } - else if (actionConstraint is ConsumesAttribute consumesAttribute && + else if (actionConstraint is ConsumesAttribute consumesAttribute && !metadata.OfType().Any()) { metadata.Add(new ConsumesMetadata(consumesAttribute.ContentTypes.ToArray())); @@ -444,16 +444,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal public List ConventionalEndpointInfos { get; } - private class RouteNameMetadata : IRouteNameMetadata - { - public RouteNameMetadata(string routeName) - { - Name = routeName; - } - - public string Name { get; } - } - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs index dcaeaca936..3adacf3c38 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -142,7 +142,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing return Task.CompletedTask; }, RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, Http415EndpointDisplayName); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs index 6f24248950..8ddd49c2f1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs @@ -15,21 +15,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing { private readonly ILogger _logger; private readonly LinkGenerator _linkGenerator; - private readonly IEndpointFinder _routeValuesBasedEndpointFinder; /// /// Initializes a new instance of the class using the specified /// . /// /// The for the current request. - /// - /// The which finds endpoints by required route values. - /// /// The used to generate the link. /// The . public EndpointRoutingUrlHelper( ActionContext actionContext, - IEndpointFinder routeValuesBasedEndpointFinder, LinkGenerator linkGenerator, ILogger logger) : base(actionContext) @@ -45,7 +40,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing } _linkGenerator = linkGenerator; - _routeValuesBasedEndpointFinder = routeValuesBasedEndpointFinder; _logger = logger; } @@ -85,23 +79,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing valuesDictionary["controller"] = urlActionContext.Controller; } - var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( - new RouteValuesAddress() - { - ExplicitValues = valuesDictionary, - AmbientValues = AmbientValues - }); - var successfullyGeneratedLink = _linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = ActionContext.HttpContext, - Endpoints = endpoints, - ExplicitValues = valuesDictionary, - AmbientValues = AmbientValues - }, + ActionContext.HttpContext, + valuesDictionary, out var link); - if (!successfullyGeneratedLink) { //TODO: log here @@ -122,22 +103,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); - var endpoints = _routeValuesBasedEndpointFinder.FindEndpoints( - new RouteValuesAddress() - { - RouteName = routeContext.RouteName, - ExplicitValues = valuesDictionary, - AmbientValues = AmbientValues - }); - var successfullyGeneratedLink = _linkGenerator.TryGetLink( - new LinkGeneratorContext - { - HttpContext = ActionContext.HttpContext, - Endpoints = endpoints, - ExplicitValues = valuesDictionary, - AmbientValues = AmbientValues - }, + ActionContext.HttpContext, + routeContext.RouteName, + valuesDictionary, out var link); if (!successfullyGeneratedLink) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index fb6bbb0ae5..933f339f30 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -51,12 +51,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing { var services = httpContext.RequestServices; var linkGenerator = services.GetRequiredService(); - var routeValuesBasedEndpointFinder = services.GetRequiredService>(); var logger = services.GetRequiredService>(); urlHelper = new EndpointRoutingUrlHelper( context, - routeValuesBasedEndpointFinder, linkGenerator, logger); } diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs index 3c5d23010d..352a8b4ee6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs @@ -6,7 +6,7 @@ using System.Linq; using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Routing.Metadata; +using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Mvc.Cors.Internal { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index db7a1e3ce1..060543dd8b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -14,7 +14,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Routing.Metadata; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Options; using Moq; using Xunit; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 6304099fc0..bc8d0dbcdc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -66,7 +66,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - var endpointValue = matcherEndpoint.RequiredValues["Name"]; + var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeValuesAddressMetadata); + var endpointValue = routeValuesAddressMetadata.RequiredValues["Name"]; Assert.Equal(routeValue, endpointValue); Assert.Equal(displayName, matcherEndpoint.DisplayName); @@ -389,7 +391,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void Endpoints_ConventionalRoute_WithNoRouteName_DoesNotAddRouteNameMetadata() + public void Endpoints_ConventionalRoute_WithEmptyRouteName_CreatesMetadataWithEmptyRouteName() { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -404,8 +406,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); - Assert.Null(routeNameMetadata); + var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeValuesAddressNameMetadata); + Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name); } [Fact] @@ -428,17 +431,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal (ep) => { var matcherEndpoint = Assert.IsType(ep); - var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); - Assert.NotNull(routeNameMetadata); - Assert.Equal("namedRoute", routeNameMetadata.Name); + var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeValuesAddressMetadata); + Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); }, (ep) => { var matcherEndpoint = Assert.IsType(ep); - var routeNameMetadata = matcherEndpoint.Metadata.GetMetadata(); - Assert.NotNull(routeNameMetadata); - Assert.Equal("namedRoute", routeNameMetadata.Name); + var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); + Assert.NotNull(routeValuesAddressMetadata); + Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); }); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 77288f75d3..31e3dd8653 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -1,15 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Linq; -using System.Reflection; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; -using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; @@ -379,7 +375,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new MatcherEndpoint( (r) => null, RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), 0, new EndpointMetadataCollection(metadata), $"test: {action?.DisplayName}"); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs index d52ddebf74..48a49e465e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs @@ -223,7 +223,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new MatcherEndpoint( (next) => null, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, new EndpointMetadataCollection(metadata), $"test: {template} - {string.Join(", ", consumesMetadata?.ContentTypes ?? Array.Empty())}"); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index 867832e808..e02dd3c5c9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -56,7 +56,17 @@ namespace Microsoft.AspNetCore.Mvc.Routing requiredValues: new { controller = "Orders", action = "GetAll" }, routeName: "OrdersApi"); var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); - urlHelper.ActionContext.RouteData.Values["id"] = "500"; + + // Set the endpoint feature and current context just as a normal request to MVC app would be + var endpointFeature = new EndpointFeature(); + urlHelper.ActionContext.HttpContext.Features.Set(endpointFeature); + endpointFeature.Endpoint = endpoint1; + endpointFeature.Values = new RouteValueDictionary + { + ["controller"] = "Orders", + ["action"] = "GetById", + ["id"] = "500" + }; // Act var url = urlHelper.RouteUrl( @@ -132,7 +142,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing endpoints.Add(new MatcherEndpoint( next => httpContext => Task.CompletedTask, RoutePatternFactory.Parse(template), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, null)); @@ -147,7 +156,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing Endpoint = new MatcherEndpoint( next => cntxt => Task.CompletedTask, RoutePatternFactory.Parse("/"), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, null) @@ -280,17 +288,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing { if (metadataCollection == null) { - metadataCollection = EndpointMetadataCollection.Empty; - if (!string.IsNullOrEmpty(routeName)) - { - metadataCollection = new EndpointMetadataCollection(new[] { new RouteNameMetadata(routeName) }); - } + metadataCollection = new EndpointMetadataCollection( + new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues))); } return new MatcherEndpoint( next => (httpContext) => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(requiredValues), order, metadataCollection, null); @@ -316,22 +320,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new MatcherEndpoint( next => c => Task.CompletedTask, RoutePatternFactory.Parse(template, defaults, constraints: null), - new RouteValueDictionary(), 0, EndpointMetadataCollection.Empty, null); } - private class RouteNameMetadata : IRouteNameMetadata - { - public RouteNameMetadata(string routeName) - { - Name = routeName; - } - - public string Name { get; } - } - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs index a9538a9b24..4543e2e8cd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs @@ -11,7 +11,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Routing.Metadata; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; From 4336b503404dee02c50a12ceeef52440a282a8e7 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Sat, 4 Aug 2018 08:10:53 -0700 Subject: [PATCH 165/316] Updated dependencies --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 3015d4e3c2..190cdd199b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 2.2.0-preview1-17102 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-a-preview1-routing-lg-16736 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-a-preview1-change-linkgenerator-api-16845 - 2.2.0-a-preview1-change-linkgenerator-api-16845 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 1.7.0 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 2.1.0 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34869 - 2.2.0-preview1-34869 + 2.2.0-preview1-34889 + 2.2.0-preview1-34889 15.6.1 4.7.49 2.0.3 From f067c3964fd9b438a0eb42dac0e0a5fa57357237 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Mon, 6 Aug 2018 20:45:11 +0000 Subject: [PATCH 166/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 190cdd199b..73864053cb 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34889 - 2.2.0-preview1-17102 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 + 2.2.0-preview1-34896 + 2.2.0-preview1-20180731.1 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34889 + 2.2.0-preview1-34896 1.7.0 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 2.1.0 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34889 - 2.2.0-preview1-34889 + 2.2.0-preview1-34896 + 2.2.0-preview1-34896 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 6b8da29e6b..c7af2292c7 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-17102 -commithash:e7e2b5a97ca92cfc6acc4def534cb0901a6d1eb9 +version:2.2.0-preview1-20180731.1 +commithash:29fde58465439f4bb9df40830635ed758e063daf From 47f2f451e3a55434915ce0b33be473148787b5c2 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 7 Aug 2018 09:22:33 +1200 Subject: [PATCH 167/316] EnableEndpointRouting documentation (#8220) --- .../MvcOptions.cs | 29 ++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index eb80659155..cafe4e746e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -68,7 +68,34 @@ namespace Microsoft.AspNetCore.Mvc }; } - // REVIEW: Add documentation + /// + /// Gets or sets a value that determines if routing should use endpoints internally, or if legacy routing + /// logic should be used. Endpoint routing is used to match HTTP requests to MVC actions, and to generate + /// URLs with . + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless explicitly configured. + /// + /// public bool EnableEndpointRouting { get => _enableEndpointRouting.Value; From 685176faf5674b7b3608be718a959ef9bd64ebde Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 7 Aug 2018 12:14:33 -0700 Subject: [PATCH 168/316] Updated dependencies --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 73864053cb..36f8046469 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 1.7.0 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 2.1.0 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34896 - 2.2.0-preview1-34896 + 2.2.0-preview1-34897 + 2.2.0-preview1-34897 15.6.1 4.7.49 2.0.3 From 9585084258eca8ee4ff9abfe64185dc2ab3eca96 Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 7 Aug 2018 13:38:36 -0700 Subject: [PATCH 169/316] Using Routing feature branch versions --- build/dependencies.props | 4 ++-- .../Builder/MvcApplicationBuilderExtensions.cs | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 36f8046469..ebe63156ba 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34897 2.2.0-preview1-34897 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 + 2.2.0-a-preview1-useglobalrouting-removed-16860 + 2.2.0-a-preview1-useglobalrouting-removed-16860 2.2.0-preview1-34897 2.2.0-preview1-34897 2.2.0-preview1-34897 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index 08baf62c2e..c54fcb350a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; @@ -125,7 +126,7 @@ namespace Microsoft.AspNetCore.Builder { // Matching middleware has not been registered yet // For back-compat register middleware so an endpoint is matched and then immediately used - app.UseGlobalRouting(); + app.UseEndpointRouting(); } return app.UseEndpoint(); From 3c19cede7da805afebe1a525ae0f63778351e4bf Mon Sep 17 00:00:00 2001 From: Kiran Challa Date: Tue, 7 Aug 2018 16:01:48 -0700 Subject: [PATCH 170/316] Updated dependencies --- build/dependencies.props | 144 +++++++++++++++++++-------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index ebe63156ba..c32829e4ce 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-a-preview1-useglobalrouting-removed-16860 - 2.2.0-a-preview1-useglobalrouting-removed-16860 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 1.7.0 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 2.1.0 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34897 - 2.2.0-preview1-34897 + 2.2.0-preview1-34913 + 2.2.0-preview1-34913 15.6.1 4.7.49 2.0.3 From da1189e6f12c485ddda9c5cefe01b8cde2708f3c Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 8 Aug 2018 23:41:30 +0200 Subject: [PATCH 171/316] Use StatusCodes constants instead of literals in the ProducesResponseType code fix (#8234) * Use StatusCodes constants instead of literals --- .../AddResponseTypeAttributeCodeFixAction.cs | 80 +++++++++++++++++-- .../ApiSymbolNames.cs | 2 + ...llyQualifiedProducesResponseType.Output.cs | 9 ++- .../CodeFixAddsMissingStatusCodes.Input.cs | 6 +- .../CodeFixAddsMissingStatusCodes.Output.cs | 10 ++- .../CodeFixAddsStatusCodes.Output.cs | 8 +- .../CodeFixAddsSuccessStatusCode.Output.cs | 7 +- ...ConventionAddsMissingStatusCodes.Output.cs | 5 +- ...tionMethodAddsMissingStatusCodes.Output.cs | 8 +- 9 files changed, 106 insertions(+), 29 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs index 27a7ca1c35..9457429a12 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs @@ -40,9 +40,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); + + var addUsingDirective = false; foreach (var statusCode in statusCodes.OrderBy(s => s)) { - documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(statusCode)); + documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(context, statusCode, out var addUsing)); + addUsingDirective |= addUsing; } if (!declaredResponseMetadata.Any(m => m.IsDefault && m.AttributeSource == context.Method)) @@ -64,7 +67,23 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers documentEditor.RemoveNode(attributeSyntax); } - return documentEditor.GetChangedDocument(); + var document = documentEditor.GetChangedDocument(); + + var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false); + + if (root is CompilationUnitSyntax compilationUnit && addUsingDirective) + { + const string @namespace = "Microsoft.AspNetCore.Http"; + + var declaredUsings = new HashSet(compilationUnit.Usings.Select(x => x.Name.ToString())); + + if (!declaredUsings.Contains(@namespace)) + { + root = compilationUnit.AddUsings(SyntaxFactory.UsingDirective(SyntaxFactory.ParseName(@namespace))); + } + } + + return document.WithSyntaxRoot(root); } private async Task CreateCodeActionContext(CancellationToken cancellationToken) @@ -75,12 +94,37 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers var methodSyntax = methodReturnStatement.FirstAncestorOrSelf(); var method = semanticModel.GetDeclaredSymbol(methodSyntax, cancellationToken); + var statusCodesType = semanticModel.Compilation.GetTypeByMetadataName(ApiSymbolNames.HttpStatusCodes); + var statusCodeConstants = GetStatusCodeConstants(statusCodesType); + var symbolCache = new ApiControllerSymbolCache(semanticModel.Compilation); - var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, cancellationToken); + var codeActionContext = new CodeActionContext(semanticModel, symbolCache, method, methodSyntax, statusCodeConstants, cancellationToken); return codeActionContext; } + private static Dictionary GetStatusCodeConstants(INamespaceOrTypeSymbol statusCodesType) + { + var statusCodeConstants = new Dictionary(); + + if (statusCodesType != null) + { + foreach (var member in statusCodesType.GetMembers()) + { + if (member is IFieldSymbol field && + field.Type.SpecialType == SpecialType.System_Int32 && + field.Name.StartsWith("Status") && + field.HasConstantValue && + field.ConstantValue is int statusCode) + { + statusCodeConstants[statusCode] = field.Name; + } + } + } + + return statusCodeConstants; + } + private ICollection CalculateStatusCodesToApply(CodeActionContext context, IList declaredResponseMetadata) { if (!SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata)) @@ -105,14 +149,31 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return statusCodes; } - private static AttributeSyntax CreateProducesResponseTypeAttribute(int statusCode) + private static AttributeSyntax CreateProducesResponseTypeAttribute(CodeActionContext context, int statusCode, out bool addUsingDirective) { + var statusCodeSyntax = CreateStatusCodeSyntax(context, statusCode, out addUsingDirective); + return SyntaxFactory.Attribute( SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute) .WithAdditionalAnnotations(Simplifier.Annotation), SyntaxFactory.AttributeArgumentList().AddArguments( - SyntaxFactory.AttributeArgument( - SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode))))); + SyntaxFactory.AttributeArgument(statusCodeSyntax))); + } + + private static ExpressionSyntax CreateStatusCodeSyntax(CodeActionContext context, int statusCode, out bool addUsingDirective) + { + if (context.StatusCodeConstants.TryGetValue(statusCode, out var constantName)) + { + addUsingDirective = true; + return SyntaxFactory.MemberAccessExpression( + SyntaxKind.SimpleMemberAccessExpression, + SyntaxFactory.ParseTypeName(ApiSymbolNames.HttpStatusCodes) + .WithAdditionalAnnotations(Simplifier.Annotation), + SyntaxFactory.IdentifierName(constantName)); + } + + addUsingDirective = false; + return SyntaxFactory.LiteralExpression(SyntaxKind.NumericLiteralExpression, SyntaxFactory.Literal(statusCode)); } private static AttributeSyntax CreateProducesDefaultResponseTypeAttribute() @@ -124,22 +185,25 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers private readonly struct CodeActionContext { - public CodeActionContext( - SemanticModel semanticModel, + public CodeActionContext(SemanticModel semanticModel, ApiControllerSymbolCache symbolCache, IMethodSymbol method, MethodDeclarationSyntax methodSyntax, + Dictionary statusCodeConstants, CancellationToken cancellationToken) { SemanticModel = semanticModel; SymbolCache = symbolCache; Method = method; MethodSyntax = methodSyntax; + StatusCodeConstants = statusCodeConstants; CancellationToken = cancellationToken; } public MethodDeclarationSyntax MethodSyntax { get; } + public Dictionary StatusCodeConstants { get; } + public IMethodSymbol Method { get; } public SemanticModel SemanticModel { get; } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs index 9ec9bdfa0f..0771fafc22 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs @@ -32,5 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute"; public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; + + public const string HttpStatusCodes = "Microsoft.AspNetCore.Http.StatusCodes"; } } diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs index 67c467796b..accec3f2d4 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs @@ -1,4 +1,5 @@ - +using Microsoft.AspNetCore.Http; + [assembly: Microsoft.AspNetCore.Mvc.ApiConventionType(typeof(Microsoft.AspNetCore.Mvc.DefaultApiConventions))] namespace TestApp._OUTPUT_ @@ -17,9 +18,9 @@ namespace TestApp._OUTPUT_ { public class CodeFixAddsFullyQualifiedProducesResponseType : BaseController { - [Microsoft.AspNetCore.Mvc.ProducesResponseType(202)] - [Microsoft.AspNetCore.Mvc.ProducesResponseType(400)] - [Microsoft.AspNetCore.Mvc.ProducesResponseType(404)] + [Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status202Accepted)] + [Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status400BadRequest)] + [Microsoft.AspNetCore.Mvc.ProducesResponseType(StatusCodes.Status404NotFound)] [Microsoft.AspNetCore.Mvc.ProducesDefaultResponseType] public object GetItem(int id) { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs index a2992eb108..65eb3a68ae 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs @@ -1,10 +1,12 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ { [ApiController] [Route("[controller]/[action]")] public class CodeFixAddsMissingStatusCodes : ControllerBase { - [ProducesResponseType(404)] + [ProducesResponseType(StatusCodes.Status404NotFound)] public IActionResult GetItem(int id) { if (id == 0) diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs index 136b633726..8453b10ac5 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs @@ -1,12 +1,14 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] public class CodeFixAddsMissingStatusCodes : ControllerBase { - [ProducesResponseType(404)] - [ProducesResponseType(200)] - [ProducesResponseType(400)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesDefaultResponseType] public IActionResult GetItem(int id) { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs index 09e944a4b8..0da7b06e5c 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs @@ -1,11 +1,13 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] public class CodeFixAddsStatusCodesController : ControllerBase { - [ProducesResponseType(200)] - [ProducesResponseType(404)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public IActionResult GetItem(int id) { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs index d51b75a841..dc64099289 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; [assembly: ApiConventionType(typeof(DefaultApiConventions))] @@ -8,9 +9,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ [Route("[controller]/[action]")] public class CodeFixAddsSuccessStatusCode : ControllerBase { - [ProducesResponseType(201)] - [ProducesResponseType(400)] - [ProducesResponseType(404)] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public ActionResult GetItem(string id) { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs index 94e564948d..657b5e9690 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Http; [assembly: ApiConventionType(typeof(DefaultApiConventions))] @@ -8,8 +9,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ [Route("[controller]/[action]")] public class CodeFixWithConventionAddsMissingStatusCodes : ControllerBase { - [ProducesResponseType(202)] - [ProducesResponseType(404)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public ActionResult GetItem(int id) { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs index 24be3e391c..d8d7215a59 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs @@ -1,11 +1,13 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ { [ApiController] [Route("[controller]/[action]")] public class CodeFixWithConventionMethodAddsMissingStatusCodes : ControllerBase { - [ProducesResponseType(202)] - [ProducesResponseType(404)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status404NotFound)] [ProducesDefaultResponseType] public ActionResult GetItem(int id) { From 9da8e2c908de2aac5671da22358d7930790ec66d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 9 Aug 2018 10:03:10 -0700 Subject: [PATCH 172/316] Ensure UnsupportedContentTypeFilter runs before ModelStateInvalidFilter Both UnsupportedContentTypeFilter and ModelStateInvalidFilter use ModelState to determine the response. UnsupportedContentTypeFilter returns a more specific response and should execute earlier than the latter filter. Fixes #8236 --- .../UnsupportedContentTypeFilter.cs | 13 ++++++-- .../ApiBehaviorTest.cs | 33 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs index 0c134d7dc9..fab0cbe68f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; namespace Microsoft.AspNetCore.Mvc.ModelBinding { @@ -10,8 +11,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// and short-circuits the pipeline /// with an Unsupported Media Type (415) response. /// - public class UnsupportedContentTypeFilter : IActionFilter + public class UnsupportedContentTypeFilter : IActionFilter, IOrderedFilter { + /// + /// Gets or sets the filter order. . + /// + /// Defaults to -3000 to ensure it executes before . + /// + /// + public int Order { get; set; } = -3000; + /// public void OnActionExecuting(ActionExecutingContext context) { @@ -32,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding foreach (var kvp in modelState) { var errors = kvp.Value.Errors; - for (int i = 0; i < errors.Count; i++) + for (var i = 0; i < errors.Count; i++) { var error = errors[i]; if (error.Exception is UnsupportedContentTypeException) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index b27a549321..cfb5df0887 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -5,6 +5,7 @@ using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Text; using System.Threading.Tasks; using BasicWebSite.Models; using Newtonsoft.Json; @@ -58,6 +59,38 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests ); } + [Fact] + public async Task ActionsReturnUnsupportedMediaType_WhenMediaTypeIsNotSupported() + { + // Arrange + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/contact") + { + Content = new StringContent("some content", Encoding.UTF8, "text/css"), + }; + + // Act + var response = await Client.SendAsync(requestMessage); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.UnsupportedMediaType); + } + + [Fact] + public async Task ActionsReturnUnsupportedMediaType_WhenEncodingIsUnsupported() + { + // Arrange + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "/contact") + { + Content = new StringContent("some content", Encoding.UTF7, "application/json"), + }; + + // Act + var response = await Client.SendAsync(requestMessage); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.UnsupportedMediaType); + } + [Fact] public async Task ActionsReturnBadRequest_UsesProblemDescriptionProviderAndApiConventionsToConfigureErrorResponse() { From b78063d2d4eab19d89bb3525981e6fe1a8a4da3e Mon Sep 17 00:00:00 2001 From: Ikebe Shodai Date: Sat, 11 Aug 2018 02:53:52 +0900 Subject: [PATCH 173/316] Fix locale sensitive unit tests --- .../ModelBinding/ParameterBinderTest.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index 5b67a06b92..d82f37de92 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -16,6 +16,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; @@ -498,6 +499,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } [Fact] + [ReplaceCulture] public async Task BindModelAsync_ForParameter_UsesValidationFromActualModel_WhenDerivedModelIsSet() { // Arrange @@ -606,6 +608,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } [Fact] + [ReplaceCulture] public async Task BindModelAsync_ForProperty_UsesValidationFromActualModel_WhenDerivedModelIsSet() { // Arrange From 253ac7c1430c1dc00e9fd5a8a1ad2d9131a5e526 Mon Sep 17 00:00:00 2001 From: Ikebe Shodai Date: Fri, 10 Aug 2018 20:04:03 +0900 Subject: [PATCH 174/316] Ignore line ending diff --- .../RazorPagesWithBasePathTest.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 0a645b9e03..462e83326d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -330,7 +330,7 @@ Hello from page"; var response = await Client.GetStringAsync("/Accounts/PageWithLinks"); // Assert - Assert.Equal(expected, response.Trim()); + Assert.Equal(expected, response.Trim(), ignoreLineEndingDifferences: true); } [Fact] @@ -346,7 +346,7 @@ Hello from page"; var response = await Client.GetStringAsync("/Accounts/RelativeLinks"); // Assert - Assert.Equal(expected, response.Trim()); + Assert.Equal(expected, response.Trim(), ignoreLineEndingDifferences: true); } [Fact] @@ -369,7 +369,7 @@ Hello from /Pages/Shared/"; var response = await Client.GetStringAsync("/Accounts/Manage/RenderPartials"); // Assert - Assert.Equal(expected, response.Trim()); + Assert.Equal(expected, response.Trim(), ignoreLineEndingDifferences: true); } [Fact] From 93d9c11778662eed82d1022b1e8b3f5a2324d69c Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 12 Aug 2018 19:22:44 +0000 Subject: [PATCH 175/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index c32829e4ce..7fbb2a958b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34913 - 2.2.0-preview1-20180731.1 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 + 2.2.0-preview1-34967 + 2.2.0-preview1-20180807.2 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34913 + 2.2.0-preview1-34967 1.7.0 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 2.1.0 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34913 - 2.2.0-preview1-34913 + 2.2.0-preview1-34967 + 2.2.0-preview1-34967 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index c7af2292c7..3fbcc80189 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180731.1 -commithash:29fde58465439f4bb9df40830635ed758e063daf +version:2.2.0-preview1-20180807.2 +commithash:11495dbd236104434e08cb1152fcb58cf2a20923 From 2421ae8c833b7cbb1991e43c8dda9632f9e2d61a Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Tue, 14 Aug 2018 00:39:20 +0200 Subject: [PATCH 176/316] Add IStatusCodeActionResult (#8265) * Add IStatusCodeActionResult * Add unit test for explicitly implemented property on StatusCodeResult --- .../ContentResult.cs | 2 +- .../Infrastructure/IStatusCodeActionResult.cs | 17 +++++++++++++++++ .../ObjectResult.cs | 2 +- .../StatusCodeResult.cs | 5 ++++- .../JsonResult.cs | 3 ++- .../PartialViewResult.cs | 2 +- .../ViewComponentResult.cs | 2 +- .../ViewResult.cs | 2 +- .../HttpStatusCodeResultTests.cs | 14 ++++++++++++++ 9 files changed, 42 insertions(+), 7 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs index 65ad59bf47..29c9ddab2d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc { - public class ContentResult : ActionResult + public class ContentResult : ActionResult, IStatusCodeActionResult { /// /// Gets or set the content representing the body of the response. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs new file mode 100644 index 0000000000..a8eabf652d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs @@ -0,0 +1,17 @@ +// 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.Infrastructure +{ + /// + /// Represents an that when executed will + /// produce an HTTP response with the specified . + /// + public interface IStatusCodeActionResult : IActionResult + { + /// + /// Gets or sets the HTTP status code. + /// + int? StatusCode { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs index 8e850a363c..e81dbccfa1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc { - public class ObjectResult : ActionResult + public class ObjectResult : ActionResult, IStatusCodeActionResult { public ObjectResult(object value) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs index 8db690c94e..305d279372 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc /// Represents an that when executed will /// produce an HTTP response with the given response status code. /// - public class StatusCodeResult : ActionResult + public class StatusCodeResult : ActionResult, IStatusCodeActionResult { /// /// Initializes a new instance of the class @@ -29,6 +30,8 @@ namespace Microsoft.AspNetCore.Mvc /// public int StatusCode { get; } + int? IStatusCodeActionResult.StatusCode => StatusCode; + /// public override void ExecuteResult(ActionContext context) { diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs index 4380224057..a4a576a83f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs @@ -4,6 +4,7 @@ using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; @@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// An action result which formats the given object as JSON. /// - public class JsonResult : ActionResult + public class JsonResult : ActionResult, IStatusCodeActionResult { /// /// Creates a new with the given . diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs index e9f2763057..56b0f80139 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Represents an that renders a partial view to the response. /// - public class PartialViewResult : ActionResult + public class PartialViewResult : ActionResult, IStatusCodeActionResult { /// /// Gets or sets the HTTP status code. diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs index 898f676067..cbdb85574c 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// An which renders a view component to the response. /// - public class ViewComponentResult : ActionResult + public class ViewComponentResult : ActionResult, IStatusCodeActionResult { /// /// Gets or sets the arguments provided to the view component. diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs index c98562fc9e..5534ec8666 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Represents an that renders a view to the response. /// - public class ViewResult : ActionResult + public class ViewResult : ActionResult, IStatusCodeActionResult { /// /// Gets or sets the HTTP status code. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs index e69fabaf5e..48634917fb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -32,6 +33,19 @@ namespace Microsoft.AspNetCore.Mvc Assert.Equal(StatusCodes.Status404NotFound, httpContext.Response.StatusCode); } + [Fact] + public void HttpStatusCodeResult_ReturnsCorrectStatusCodeAsIStatusCodeActionResult() + { + // Arrange + var result = new StatusCodeResult(StatusCodes.Status404NotFound); + + // Act + var statusResult = result as IStatusCodeActionResult; + + // Assert + Assert.Equal(StatusCodes.Status404NotFound, statusResult?.StatusCode); + } + private static IServiceCollection CreateServices() { var services = new ServiceCollection(); From 92f1dbe16cfd945d9759f290ae7e00fd1efa6e5a Mon Sep 17 00:00:00 2001 From: Mikael Mengistu Date: Tue, 14 Aug 2018 10:36:57 -0700 Subject: [PATCH 177/316] Remove AppVeyor badge from README (#8271) --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index e5aa14d771..a121f8b427 100644 --- a/README.md +++ b/README.md @@ -3,8 +3,6 @@ ASP.NET Core MVC **Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack** -AppVeyor: [![AppVeyor](https://ci.appveyor.com/api/projects/status/969jbosi0qwc1awg/branch/dev?svg=true)](https://ci.appveyor.com/project/aspnetci/mvc/branch/dev) - Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=dev)](https://travis-ci.org/aspnet/Mvc) ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards. From 7e25d7908a599c7c88718ad558894c57718cac5f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 7 Aug 2018 17:49:50 -0700 Subject: [PATCH 178/316] Warn when the parameter name for a model bound complex parameter has the same name as a top level property Fixes #7753 --- .../DiagnosticDescriptors.cs | 10 + .../MvcFacts.cs | 2 +- .../SymbolNames.cs | 6 + .../TopLevelParameterNameAnalyzer.cs | 174 +++++++++++++ .../ApiControllerFacts.cs | 1 + ...rosoft.AspNetCore.Mvc.Api.Analyzers.csproj | 1 + ...ctionsWithParametersThatMatchProperties.cs | 13 + ...ticsAreReturned_ForModelBoundParameters.cs | 17 ++ ...NameProviderIsUsedToModifyParameterName.cs | 15 ++ .../GetNameTests.cs | 13 + .../IsProblematicParameter_IgnoresFields.cs | 9 + .../IsProblematicParameter_IgnoresMethods.cs | 9 + ...ticParameter_IgnoresNonPublicProperties.cs | 9 + ...ematicParameter_IgnoresStaticProperties.cs | 9 + ...meter_ReturnsFalse_ForFromBodyParameter.cs | 9 + ...lBinderAttributeIsUsedToRenameParameter.cs | 9 + ...elBinderAttributeIsUsedToRenameProperty.cs | 10 + ...IfParameterNameIsTheSameAsModelProperty.cs | 9 + ...erAttributeIsTheSameNameAsModelProperty.cs | 9 + ...lBindingAttributeHasSameNameAsParameter.cs | 10 + ...DiagnosticsAreReturnedForApiControllers.cs | 14 ++ .../NoDiagnosticsAreReturnedForNonActions.cs | 13 + ...ParameterIsRenamedUsingBindingAttribute.cs | 13 + .../TopLevelParameterNameAnalyzerTest.cs | 237 ++++++++++++++++++ test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs | 1 + 25 files changed, 621 insertions(+), 1 deletion(-) rename src/{Microsoft.AspNetCore.Mvc.Api.Analyzers => Microsoft.AspNetCore.Mvc.Analyzers}/MvcFacts.cs (98%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs create mode 100644 test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs index b2684297bd..8b0aa761d1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs @@ -42,5 +42,15 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers "Usage", DiagnosticSeverity.Warning, isEnabledByDefault: true); + + public static readonly DiagnosticDescriptor MVC1004_ParameterNameCollidesWithTopLevelProperty = + new DiagnosticDescriptor( + "MVC1004", + "Rename model bound parameter.", + "Property on type '{0}' has the same name as parameter '{1}'. This may result in incorrect model binding. Consider renaming the parameter or using a model binding attribute to override the name.", + "Naming", + DiagnosticSeverity.Warning, + isEnabledByDefault: true, + helpLinkUri: "https://aka.ms/AA20pbc"); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs similarity index 98% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs rename to src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs index a5dfe71dcc..90651247c4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/MvcFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs @@ -5,7 +5,7 @@ using System; using System.Diagnostics; using Microsoft.CodeAnalysis; -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +namespace Microsoft.AspNetCore.Mvc.Analyzers { internal static class MvcFacts { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index ca00478262..8448c01337 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -19,10 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string AuthorizeAttribute = "Microsoft.AspNetCore.Authorization.AuthorizeAttribute"; + public const string BindAttribute = "Microsoft.AspNetCore.Mvc.BindAttribute"; + public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; + public const string FromBodyAttribute = "Microsoft.AspNetCore.Mvc.FromBodyAttribute"; + public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; @@ -35,6 +39,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string IHtmlHelperType = "Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper"; + public const string IModelNameProvider = "Microsoft.AspNetCore.Mvc.ModelBinding.IModelNameProvider"; + public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs new file mode 100644 index 0000000000..22f0ba92ba --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs @@ -0,0 +1,174 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class TopLevelParameterNameAnalyzer : DiagnosticAnalyzer + { + public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create( + DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty); + + public override void Initialize(AnalysisContext context) + { + context.EnableConcurrentExecution(); + context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.None); + + context.RegisterCompilationStartAction(compilationStartAnalysisContext => + { + var typeCache = new SymbolCache(compilationStartAnalysisContext.Compilation); + if (typeCache.ControllerAttribute == null || typeCache.ControllerAttribute.TypeKind == TypeKind.Error) + { + // No-op if we can't find types we care about. + return; + } + + InitializeWorker(compilationStartAnalysisContext, typeCache); + }); + } + + private void InitializeWorker(CompilationStartAnalysisContext compilationStartAnalysisContext, SymbolCache symbolCache) + { + compilationStartAnalysisContext.RegisterSymbolAction(symbolAnalysisContext => + { + var method = (IMethodSymbol)symbolAnalysisContext.Symbol; + if (method.MethodKind != MethodKind.Ordinary) + { + return; + } + + if (method.Parameters.Length == 0) + { + return; + } + + if (!MvcFacts.IsController(method.ContainingType, symbolCache.ControllerAttribute, symbolCache.NonControllerAttribute) || + !MvcFacts.IsControllerAction(method, symbolCache.NonActionAttribute, symbolCache.IDisposableDispose)) + { + return; + } + + if (method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) + { + // The issue of parameter name collision with properties affects complex model-bound types + // and not input formatting. Ignore ApiController instances since they default to formatting. + return; + } + + for (var i = 0; i < method.Parameters.Length; i++) + { + var parameter = method.Parameters[i]; + if (IsProblematicParameter(symbolCache, parameter)) + { + var location = parameter.Locations.Length != 0 ? + parameter.Locations[0] : + Location.None; + + symbolAnalysisContext.ReportDiagnostic( + Diagnostic.Create( + DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty, + location, + parameter.Type.Name, + parameter.Name)); + } + } + }, SymbolKind.Method); + } + + internal static bool IsProblematicParameter(in SymbolCache symbolCache, IParameterSymbol parameter) + { + if (parameter.GetAttributes(symbolCache.FromBodyAttribute).Any()) + { + // Ignore input formatted parameters. + return false; + } + + var parameterName = GetName(symbolCache, parameter); + + var type = parameter.Type; + while (type != null) + { + foreach (var member in type.GetMembers()) + { + if (member.DeclaredAccessibility != Accessibility.Public || + member.IsStatic || + member.Kind != SymbolKind.Property) + { + continue; + } + + var propertyName = GetName(symbolCache, member); + if (string.Equals(parameterName, propertyName, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + } + + type = type.BaseType; + } + + return false; + } + + internal static string GetName(in SymbolCache symbolCache, ISymbol symbol) + { + foreach (var attribute in symbol.GetAttributes(symbolCache.IModelNameProvider)) + { + // BindAttribute uses the Prefix property as an alias for IModelNameProvider.Name + var nameProperty = attribute.AttributeClass == symbolCache.BindAttribute ? "Prefix" : "Name"; + + // All of the built-in attributes (FromQueryAttribute, ModelBinderAttribute etc) only support setting the name via + // a property. We'll ignore constructor values. + for (var i = 0; i < attribute.NamedArguments.Length; i++) + { + var namedArgument = attribute.NamedArguments[i]; + var namedArgumentValue = namedArgument.Value; + if (string.Equals(namedArgument.Key, nameProperty, StringComparison.Ordinal) && + namedArgumentValue.Kind == TypedConstantKind.Primitive && + namedArgumentValue.Type.SpecialType == SpecialType.System_String && + namedArgumentValue.Value is string name) + { + return name; + } + } + } + + return symbol.Name; + } + + internal readonly struct SymbolCache + { + public SymbolCache(Compilation compilation) + { + BindAttribute = compilation.GetTypeByMetadataName(SymbolNames.BindAttribute); + ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute); + FromBodyAttribute = compilation.GetTypeByMetadataName(SymbolNames.FromBodyAttribute); + IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata); + IModelNameProvider = compilation.GetTypeByMetadataName(SymbolNames.IModelNameProvider); + NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); + NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); + + var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); + var members = disposable.GetMembers(nameof(IDisposable.Dispose)); + IDisposableDispose = members.Length == 1 ? (IMethodSymbol)members[0] : null; + } + + public INamedTypeSymbol BindAttribute { get; } + public INamedTypeSymbol ControllerAttribute { get; } + public INamedTypeSymbol FromBodyAttribute { get; } + public INamedTypeSymbol IApiBehaviorMetadata { get; } + public INamedTypeSymbol IModelNameProvider { get; } + public INamedTypeSymbol NonControllerAttribute { get; } + public INamedTypeSymbol NonActionAttribute { get; } + public IMethodSymbol IDisposableDispose { get; } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs index b947b8dc5d..84534a5d9f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs @@ -1,6 +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.Analyzers; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj index 5081b0d502..a4c48adc4b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj @@ -12,6 +12,7 @@ + diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs new file mode 100644 index 0000000000..85642db52a --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs @@ -0,0 +1,13 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties : Controller + { + [HttpPost] + public IActionResult EditPerson(DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel /*MM*/model) => null; + } + + public class DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel + { + public string Model { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs new file mode 100644 index 0000000000..be2bc2d7f0 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class DiagnosticsAreReturned_ForModelBoundParameters : Controller + { + [HttpPost] + public IActionResult EditPerson( + [FromBody] DiagnosticsAreReturned_ForModelBoundParametersModel model, + [FromQuery] DiagnosticsAreReturned_ForModelBoundParametersModel /*MM*/value) => null; + } + + public class DiagnosticsAreReturned_ForModelBoundParametersModel + { + public string Model { get; } + + public string Value { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs new file mode 100644 index 0000000000..ed15be578a --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs @@ -0,0 +1,15 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName : Controller + { + [HttpPost] + public IActionResult Edit([ModelBinder(Name = "model")] DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel /*MM*/parameter) => null; + } + + public class DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel + { + public string Model { get; } + + public string Value { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs new file mode 100644 index 0000000000..2810ab1f49 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs @@ -0,0 +1,13 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class GetNameTests + { + public void NoAttribute(int param) { } + + public void SingleAttribute([ModelBinder(Name = "testModelName")] int param) { } + + public void SingleAttributeWithoutName([ModelBinder] int param) { } + + public void MultipleAttributes([ModelBinder(Name = "name1")][Bind(Prefix = "name2")] int param) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs new file mode 100644 index 0000000000..e0136bc5f3 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_IgnoresFields + { + public string model; + + public void ActionMethod(IsProblematicParameter_IgnoresFields model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs new file mode 100644 index 0000000000..b5f79812d6 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_IgnoresMethods + { + public string Item() => null; + + public void ActionMethod(IsProblematicParameter_IgnoresMethods item) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs new file mode 100644 index 0000000000..fbfb437f54 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_IgnoresNonPublicProperties + { + protected string Model { get; set; } + + public void ActionMethod(IsProblematicParameter_IgnoresNonPublicProperties model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs new file mode 100644 index 0000000000..065fca633f --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_IgnoresStaticProperties + { + public static string Model { get; set; } + + public void ActionMethod(IsProblematicParameter_IgnoresStaticProperties model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs new file mode 100644 index 0000000000..f1b0011291 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsFalse_ForFromBodyParameter + { + public string Model { get; set; } + + public void ActionMethod([FromBody] IsProblematicParameter_ReturnsFalse_ForFromBodyParameter model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs new file mode 100644 index 0000000000..4c83838f02 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter + { + public string Model { get; set; } + + public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs new file mode 100644 index 0000000000..e4a565bd62 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty + { + [FromQuery(Name = "different")] + public string Model { get; set; } + + public void ActionMethod([Bind(Prefix = nameof(Model))] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty different) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs new file mode 100644 index 0000000000..adfcf507d0 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty + { + public string Model { get; set; } + + public void ActionMethod(IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs new file mode 100644 index 0000000000..2b96ecc579 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty + { + public string Model { get; set; } + + public void ActionMethod([Bind(Prefix = "model")] IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty different) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs new file mode 100644 index 0000000000..7f3d9c499d --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter + { + [ModelBinder(typeof(object), Name = "model")] + public string Different { get; set; } + + public void ActionMethod(IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs new file mode 100644 index 0000000000..3be2d2a07a --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs @@ -0,0 +1,14 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + [ApiController] + public class NoDiagnosticsAreReturnedForApiControllers : Controller + { + [HttpPost] + public IActionResult EditPerson(NoDiagnosticsAreReturnedForApiControllersModel model) => null; + } + + public class NoDiagnosticsAreReturnedForApiControllersModel + { + public string Model { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs new file mode 100644 index 0000000000..d6e7edce31 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs @@ -0,0 +1,13 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class NoDiagnosticsAreReturnedForNonActions : Controller + { + [NonAction] + public IActionResult EditPerson(NoDiagnosticsAreReturnedForNonActionsModel model) => null; + } + + public class NoDiagnosticsAreReturnedForNonActionsModel + { + public string Model { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs new file mode 100644 index 0000000000..e870af3704 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs @@ -0,0 +1,13 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute : Controller + { + [HttpPost] + public IActionResult EditPerson([FromForm(Name = "")] NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttributeModel model) => null; + } + + public class NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttributeModel + { + public string Model { get; } + } +} diff --git a/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs b/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs new file mode 100644 index 0000000000..e5add91ee2 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs @@ -0,0 +1,237 @@ +// 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.Linq; +using System.Runtime.CompilerServices; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles; +using Microsoft.CodeAnalysis; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Analyzers +{ + public class TopLevelParameterNameAnalyzerTest + { + private MvcDiagnosticAnalyzerRunner Runner { get; } = new MvcDiagnosticAnalyzerRunner(new TopLevelParameterNameAnalyzer()); + + [Fact] + public Task DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties() + => RunTest(nameof(DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchPropertiesModel), "model"); + + [Fact] + public Task DiagnosticsAreReturned_ForModelBoundParameters() + => RunTest(nameof(DiagnosticsAreReturned_ForModelBoundParametersModel), "value"); + + [Fact] + public Task DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName() + => RunTest(nameof(DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterNameModel), "parameter"); + + [Fact] + public Task NoDiagnosticsAreReturnedForApiControllers() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public Task NoDiagnosticsAreReturnedForNonActions() + => RunNoDiagnosticsAreReturned(); + + [Fact] + public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty() + { + var result = await IsProblematicParameterTest(); + Assert.True(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty() + { + var result = await IsProblematicParameterTest(); + Assert.True(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter() + { + var result = await IsProblematicParameterTest(); + Assert.True(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsFalse_ForFromBodyParameter() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_IgnoresStaticProperties() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_IgnoresFields() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_IgnoresMethods() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + [Fact] + public async Task IsProblematicParameter_IgnoresNonPublicProperties() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + + private async Task IsProblematicParameterTest([CallerMemberName] string testMethod = "") + { + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await project.GetCompilationAsync(); + + var modelType = compilation.GetTypeByMetadataName($"Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles.{testMethod}"); + var method = (IMethodSymbol)modelType.GetMembers("ActionMethod").First(); + var parameter = method.Parameters[0]; + + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var result = TopLevelParameterNameAnalyzer.IsProblematicParameter(symbolCache, parameter); + return result; + } + + [Fact] + public async Task GetName_ReturnsValueFromFirstAttributeWithValue() + { + var methodName = nameof(GetNameTests.SingleAttribute); + var compilation = await GetCompilationForGetName(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + + var parameter = method.Parameters[0]; + var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); + + Assert.Equal("testModelName", name); + } + + [Fact] + public async Task GetName_ReturnsName_IfNoAttributesAreSpecified() + { + var methodName = nameof(GetNameTests.NoAttribute); + var compilation = await GetCompilationForGetName(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + + var parameter = method.Parameters[0]; + var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); + + Assert.Equal("param", name); + } + + [Fact] + public async Task GetName_ReturnsName_IfAttributeDoesNotSpecifyName() + { + var methodName = nameof(GetNameTests.SingleAttributeWithoutName); + var compilation = await GetCompilationForGetName(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + + var parameter = method.Parameters[0]; + var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); + + Assert.Equal("param", name); + } + + [Fact] + public async Task GetName_ReturnsFirstName_IfMultipleAttributesAreSpecified() + { + var methodName = nameof(GetNameTests.MultipleAttributes); + var compilation = await GetCompilationForGetName(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(GetNameTests).FullName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + + var parameter = method.Parameters[0]; + var name = TopLevelParameterNameAnalyzer.GetName(symbolCache, parameter); + + Assert.Equal("name1", name); + } + + private async Task GetCompilationForGetName() + { + var testSource = MvcTestSource.Read(GetType().Name, "GetNameTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await project.GetCompilationAsync(); + return compilation; + } + + private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") + { + // Arrange + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Runner.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Empty(result); + } + + private async Task RunTest(string typeName, string parameterName, [CallerMemberName] string testMethod = "") + { + // Arrange + var descriptor = DiagnosticDescriptors.MVC1004_ParameterNameCollidesWithTopLevelProperty; + var testSource = MvcTestSource.Read(GetType().Name, testMethod); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Runner.GetDiagnosticsAsync(testSource.Source); + + // Assert + Assert.Collection( + result, + diagnostic => + { + Assert.Equal(descriptor.Id, diagnostic.Id); + Assert.Same(descriptor, diagnostic.Descriptor); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), typeName, parameterName), diagnostic.GetMessage()); + }); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs b/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs index e6009e7125..0544262951 100644 --- a/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs +++ b/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Analyzers; using Microsoft.CodeAnalysis; using Xunit; From 200a70bb86ec36bfd4d3ec08c14bcd7883060dce Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Wed, 8 Aug 2018 12:42:48 -0700 Subject: [PATCH 179/316] Update doc comments --- src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs | 2 +- src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs index fc329aadc4..33a23fef25 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc string Action(UrlActionContext actionContext); /// - /// Converts a virtual (relative) path to an application absolute path. + /// Converts a virtual (relative, starting with ~/) path to an application absolute path. /// /// /// If the specified content path does not start with the tilde (~) character, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs index 93720a5826..af43e847bb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc } /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with a relative path for the specified . /// /// The . /// The page name to generate the url for. @@ -347,7 +347,7 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, values: null); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with a relative path for the specified . /// /// The . /// The page name to generate the url for. @@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler, values: null); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with a relative path for the specified . /// /// The . /// The page name to generate the url for. @@ -367,7 +367,7 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler: null, values: values); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with a relative path for the specified . /// /// The . /// The page name to generate the url for. From af770ede876670abd37aa5a034c3d82730a12746 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 15 Aug 2018 11:13:05 -0700 Subject: [PATCH 180/316] Ignore parameters that specify a model binder type --- .../SymbolNames.cs | 2 + .../TopLevelParameterNameAnalyzer.cs | 41 +++++++++++ ...alse_ForParametersWithCustomModelBinder.cs | 9 +++ ...SourceAttributeIsUsedToRenameParameter.cs} | 4 +- ...ngSourceAttributeIsUsedToRenameProperty.cs | 10 +++ ...elBinderAttributeIsUsedToRenameProperty.cs | 10 --- ...lBinderAttributeIsUsedToRenameParameter.cs | 9 +++ .../SpecifiesModelTypeTests.cs | 11 +++ .../TopLevelParameterNameAnalyzerTest.cs | 72 ++++++++++++++++++- 9 files changed, 154 insertions(+), 14 deletions(-) create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs rename test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/{IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs => IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs} (52%) create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs delete mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs create mode 100644 test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index 8448c01337..ad152657df 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -31,6 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; + public const string IBinderTypeProviderMetadata = "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata"; + public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; public const string IConvertToActionResult = "Microsoft.AspNetCore.Mvc.IConvertToActionResult"; diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs index 22f0ba92ba..ad5aeeaeab 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs @@ -91,6 +91,12 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } + if (SpecifiesModelType(symbolCache, parameter)) + { + // Ignore parameters that specify a model type. + return false; + } + var parameterName = GetName(symbolCache, parameter); var type = parameter.Type; @@ -144,6 +150,39 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return symbol.Name; } + internal static bool SpecifiesModelType(in SymbolCache symbolCache, IParameterSymbol parameterSymbol) + { + foreach (var attribute in parameterSymbol.GetAttributes(symbolCache.IBinderTypeProviderMetadata)) + { + // Look for a attribute property named BinderType being assigned. This would match + // [ModelBinder(BinderType = typeof(SomeBinder))] + for (var i = 0; i < attribute.NamedArguments.Length; i++) + { + var namedArgument = attribute.NamedArguments[i]; + var namedArgumentValue = namedArgument.Value; + if (string.Equals(namedArgument.Key, "BinderType", StringComparison.Ordinal) && + namedArgumentValue.Kind == TypedConstantKind.Type) + { + return true; + } + } + + // Look for the binder type being specified in the constructor. This would match + // [ModelBinder(typeof(SomeBinder))] + var constructorParameters = attribute.AttributeConstructor?.Parameters ?? ImmutableArray.Empty; + for (var i = 0; i < constructorParameters.Length; i++) + { + if (string.Equals(constructorParameters[i].Name, "binderType", StringComparison.Ordinal)) + { + // A constructor that requires binderType was used. + return true; + } + } + } + + return false; + } + internal readonly struct SymbolCache { public SymbolCache(Compilation compilation) @@ -152,6 +191,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers ControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.ControllerAttribute); FromBodyAttribute = compilation.GetTypeByMetadataName(SymbolNames.FromBodyAttribute); IApiBehaviorMetadata = compilation.GetTypeByMetadataName(SymbolNames.IApiBehaviorMetadata); + IBinderTypeProviderMetadata = compilation.GetTypeByMetadataName(SymbolNames.IBinderTypeProviderMetadata); IModelNameProvider = compilation.GetTypeByMetadataName(SymbolNames.IModelNameProvider); NonControllerAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonControllerAttribute); NonActionAttribute = compilation.GetTypeByMetadataName(SymbolNames.NonActionAttribute); @@ -165,6 +205,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public INamedTypeSymbol ControllerAttribute { get; } public INamedTypeSymbol FromBodyAttribute { get; } public INamedTypeSymbol IApiBehaviorMetadata { get; } + public INamedTypeSymbol IBinderTypeProviderMetadata { get; } public INamedTypeSymbol IModelNameProvider { get; } public INamedTypeSymbol NonControllerAttribute { get; } public INamedTypeSymbol NonActionAttribute { get; } diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs new file mode 100644 index 0000000000..4e86cb1df5 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder + { + public string Model { get; set; } + + public void ActionMethod([ModelBinder(typeof(object))] IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs similarity index 52% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs rename to test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs index 4c83838f02..20c2a39c3d 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter.cs +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs @@ -1,9 +1,9 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles { - public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter + public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter { public string Model { get; set; } - public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter model) { } + public void ActionMethod([FromRoute(Name = "id")] IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter model) { } } } diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs new file mode 100644 index 0000000000..902465213f --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty + { + [FromQuery(Name = "different")] + public string Model { get; set; } + + public void ActionMethod(IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs deleted file mode 100644 index e4a565bd62..0000000000 --- a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles -{ - public class IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty - { - [FromQuery(Name = "different")] - public string Model { get; set; } - - public void ActionMethod([Bind(Prefix = nameof(Model))] IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty different) { } - } -} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs new file mode 100644 index 0000000000..0c28fb1132 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs @@ -0,0 +1,9 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter + { + public string Model { get; set; } + + public void ActionMethod([ModelBinder(Name = "model")] IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter different) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs new file mode 100644 index 0000000000..e707001d87 --- /dev/null +++ b/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs @@ -0,0 +1,11 @@ +namespace Microsoft.AspNetCore.Mvc.Analyzers.TopLevelParameterNameAnalyzerTestFiles +{ + public class SpecifiesModelTypeTests + { + public void SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType([ModelBinder(Name = "Name")] object model) { } + + public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor([ModelBinder(typeof(object))] object model) { } + + public void SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty([ModelBinder(BinderType = typeof(object))] object model) { } + } +} diff --git a/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs b/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs index e5add91ee2..7329ae9d13 100644 --- a/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs +++ b/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs @@ -61,14 +61,21 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers } [Fact] - public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameParameter() + public async Task IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter() + { + var result = await IsProblematicParameterTest(); + Assert.True(result); + } + + [Fact] + public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty() { var result = await IsProblematicParameterTest(); Assert.False(result); } [Fact] - public async Task IsProblematicParameter_ReturnsFalse_IfModelBinderAttributeIsUsedToRenameProperty() + public async Task IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter() { var result = await IsProblematicParameterTest(); Assert.False(result); @@ -81,6 +88,13 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers Assert.False(result); } + [Fact] + public async Task IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder() + { + var result = await IsProblematicParameterTest(); + Assert.False(result); + } + [Fact] public async Task IsProblematicParameter_IgnoresStaticProperties() { @@ -199,6 +213,60 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return compilation; } + [Fact] + public async Task SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType() + { + var testMethod = nameof(SpecifiesModelType_ReturnsFalse_IfModelBinderDoesNotSpecifyType); + var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await project.GetCompilationAsync(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); + var method = (IMethodSymbol)type.GetMembers(testMethod).First(); + + var parameter = method.Parameters[0]; + var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); + Assert.False(result); + } + + [Fact] + public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor() + { + var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromConstructor); + var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await project.GetCompilationAsync(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); + var method = (IMethodSymbol)type.GetMembers(testMethod).First(); + + var parameter = method.Parameters[0]; + var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); + Assert.True(result); + } + + [Fact] + public async Task SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty() + { + var testMethod = nameof(SpecifiesModelType_ReturnsTrue_IfModelBinderSpecifiesTypeFromProperty); + var testSource = MvcTestSource.Read(GetType().Name, "SpecifiesModelTypeTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await project.GetCompilationAsync(); + var symbolCache = new TopLevelParameterNameAnalyzer.SymbolCache(compilation); + + var type = compilation.GetTypeByMetadataName(typeof(SpecifiesModelTypeTests).FullName); + var method = (IMethodSymbol)type.GetMembers(testMethod).First(); + + var parameter = method.Parameters[0]; + var result = TopLevelParameterNameAnalyzer.SpecifiesModelType(symbolCache, parameter); + Assert.True(result); + } + private async Task RunNoDiagnosticsAreReturned([CallerMemberName] string testMethod = "") { // Arrange From 522006d2c866add522a546aa3b4720577f1cee57 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 10 Aug 2018 11:24:12 -0700 Subject: [PATCH 181/316] [Design] Add a MaxValidationDepth option to ValidationVisitor Fixes #7357 --- .../{Internal => }/DefaultObjectValidator.cs | 19 +- .../MvcCoreServiceCollectionExtensions.cs | 2 +- ...MvcOptionsConfigureCompatibilityOptions.cs | 3 + .../NullableCompatibilitySwitch.cs | 35 ++++ .../Validation/ValidationVisitor.cs | 56 +++++ .../MvcOptions.cs | 46 +++++ .../Properties/Resources.Designer.cs | 42 ++++ .../Resources.resx | 9 + .../ControllerBaseTest.cs | 11 +- .../DefaultControllerActivatorTest.cs | 2 +- .../DefaultControllerFactoryTest.cs | 3 +- ...ptionsConfigureCompatibilityOptionsTest.cs | 83 ++++++++ .../NullableCompatibilitySwitchTest.cs | 77 +++++++ .../ControllerBinderDelegateProviderTest.cs | 195 +++++------------- .../Internal/DefaultObjectValidatorTests.cs | 102 ++++++++- .../ModelBinding/ModelBindingHelperTest.cs | 14 +- .../ModelBinding/ParameterBinderTest.cs | 24 ++- .../InputObjectValidationTests.cs | 23 ++- .../ModelBindingTestHelper.cs | 10 +- .../Internal/PageActionInvokerTest.cs | 2 +- .../Internal/PageBinderFactoryTest.cs | 9 +- .../CompatibilitySwitchIntegrationTest.cs | 4 + .../ControllerTest.cs | 2 +- .../Controllers/ValidationController.cs | 6 + .../Models/InfinitelyRecursiveModel.cs | 29 +++ .../Models/RecursiveIdentifier.cs | 18 ++ test/WebSites/FormatterWebSite/Startup.cs | 4 +- 27 files changed, 643 insertions(+), 187 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Core/{Internal => }/DefaultObjectValidator.cs (70%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs create mode 100644 test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs create mode 100644 test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs similarity index 70% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.cs rename to src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs index 0afcdb0f5e..8fedb7702b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultObjectValidator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs @@ -2,26 +2,33 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc { /// /// The default implementation of . /// - public class DefaultObjectValidator : ObjectModelValidator + internal class DefaultObjectValidator : ObjectModelValidator { + private readonly MvcOptions _mvcOptions; + /// /// Initializes a new instance of . /// /// The . /// The list of . + /// Accessor to . public DefaultObjectValidator( IModelMetadataProvider modelMetadataProvider, - IList validatorProviders) + IList validatorProviders, + MvcOptions mvcOptions) : base(modelMetadataProvider, validatorProviders) { + _mvcOptions = mvcOptions; } public override ValidationVisitor GetValidationVisitor( @@ -31,12 +38,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal IModelMetadataProvider metadataProvider, ValidationStateDictionary validationState) { - return new ValidationVisitor( + var visitor = new ValidationVisitor( actionContext, validatorProvider, validatorCache, metadataProvider, validationState); + + visitor.MaxValidationDepth = _mvcOptions.MaxValidationDepth; + + return visitor; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 3ebdc371fa..e7e7d5e0dd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -228,7 +228,7 @@ namespace Microsoft.Extensions.DependencyInjection { var options = s.GetRequiredService>().Value; var metadataProvider = s.GetRequiredService(); - return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders); + return new DefaultObjectValidator(metadataProvider, options.ModelValidatorProviders, options); }); services.TryAddSingleton(); services.TryAddSingleton(); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index 76e3a16916..dcc5243fbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure if (Version >= CompatibilityVersion.Version_2_2) { values[nameof(MvcOptions.EnableEndpointRouting)] = true; + + // Matches JsonSerializerSettingsProvider.DefaultMaxDepth + values[nameof(MvcOptions.MaxValidationDepth)] = 32; } return values; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs new file mode 100644 index 0000000000..53563ba157 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs @@ -0,0 +1,35 @@ +// 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.Infrastructure +{ + internal class NullableCompatibilitySwitch : ICompatibilitySwitch where TValue : struct + { + private TValue? _value; + + public NullableCompatibilitySwitch(string name) + { + Name = name; + } + + public bool IsValueSet { get; private set; } + + public string Name { get; } + + public TValue? Value + { + get => _value; + set + { + IsValueSet = true; + _value = value; + } + } + + object ICompatibilitySwitch.Value + { + get => Value; + set => Value = (TValue?)value; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index fa467588a8..164928c54a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; using System.Runtime.CompilerServices; +using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { @@ -15,6 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// public class ValidationVisitor { + private int? _maxValidationDepth; + /// /// Creates a new . /// @@ -76,6 +80,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation protected ModelMetadata Metadata { get; set; } protected IValidationStrategy Strategy { get; set; } + /// + /// Gets or sets the maximum depth to constrain the validation visitor when validating. + /// + /// traverses the object graph of the model being validated. For models + /// that are very deep or are infinitely recursive, validation may result in stack overflow. + /// + /// + /// When not , will throw if + /// current traversal depth exceeds the specified value. + /// + /// + public int? MaxValidationDepth + { + get => _maxValidationDepth; + set + { + if (value != null && value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maxValidationDepth = value; + } + } + /// /// Indicates whether validation of a complex type should be performed if validation fails for any of its children. The default behavior is false. /// @@ -192,6 +221,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation return true; } + if (MaxValidationDepth != null && CurrentPath.Count > MaxValidationDepth) + { + // Non cyclic but too deep an object graph. + + // Pop the current model to make ValidationStack.Dispose happy + CurrentPath.Pop(model); + + string message; + switch (metadata.MetadataKind) + { + case ModelMetadataKind.Property: + message = Resources.FormatValidationVisitor_ExceededMaxPropertyDepth(nameof(ValidationVisitor), MaxValidationDepth, metadata.Name, metadata.ContainerType); + break; + + default: + // Since the minimum depth is never 0, MetadataKind can never be Parameter. Consequently we only special case MetadataKind.Property. + message = Resources.FormatValidationVisitor_ExceededMaxDepth(nameof(ValidationVisitor), MaxValidationDepth, metadata.ModelType); + break; + } + + message += " " + Resources.FormatValidationVisitor_ExceededMaxDepthFix(nameof(MvcOptions), nameof(MvcOptions.MaxValidationDepth)); + throw new InvalidOperationException(message) + { + HelpLink = "https://aka.ms/AA21ue1", + }; + } + var entry = GetValidationEntry(model); key = entry?.Key ?? key ?? string.Empty; metadata = entry?.Metadata ?? metadata; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index cafe4e746e..34a55b4ce4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc private readonly CompatibilitySwitch _inputFormatterExceptionPolicy; private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch _enableEndpointRouting; + private readonly NullableCompatibilitySwitch _maxValidationDepth; private readonly ICompatibilitySwitch[] _switches; /// @@ -56,6 +57,7 @@ namespace Microsoft.AspNetCore.Mvc _inputFormatterExceptionPolicy = new CompatibilitySwitch(nameof(InputFormatterExceptionPolicy), InputFormatterExceptionPolicy.AllExceptions); _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); _enableEndpointRouting = new CompatibilitySwitch(nameof(EnableEndpointRouting)); + _maxValidationDepth = new NullableCompatibilitySwitch(nameof(MaxValidationDepth)); _switches = new ICompatibilitySwitch[] { @@ -65,6 +67,7 @@ namespace Microsoft.AspNetCore.Mvc _inputFormatterExceptionPolicy, _suppressBindingUndefinedValueToEnumType, _enableEndpointRouting, + _maxValidationDepth, }; } @@ -396,6 +399,49 @@ namespace Microsoft.AspNetCore.Mvc /// public bool RequireHttpsPermanent { get; set; } + /// + /// Gets or sets the maximum depth to constrain the validation visitor when validating. Set to + /// to disable this feature. + /// + /// traverses the object graph of the model being validated. For models + /// that are very deep or are infinitely recursive, validation may result in stack overflow. + /// + /// + /// When not , will throw if + /// traversing an object exceeds the maximum allowed validation depth. + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take precedence + /// over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to then + /// this setting will have the value 200 unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// earlier then this setting will have the value unless explicitly configured. + /// + /// + public int? MaxValidationDepth + { + get => _maxValidationDepth.Value; + set + { + if (value != null && value <= 0) + { + throw new ArgumentOutOfRangeException(nameof(value)); + } + + _maxValidationDepth.Value = value; + } + } + IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_switches).GetEnumerator(); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 65b63b8d27..354ce64258 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1536,6 +1536,48 @@ namespace Microsoft.AspNetCore.Mvc.Core internal static string FormatApiConventionMethod_NoMethodFound(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_NoMethodFound"), p0, p1); + /// + /// {0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'. + /// + internal static string ValidationVisitor_ExceededMaxDepth + { + get => GetString("ValidationVisitor_ExceededMaxDepth"); + } + + /// + /// {0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'. + /// + internal static string FormatValidationVisitor_ExceededMaxDepth(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxDepth"), p0, p1, p2); + + /// + /// This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type. + /// + internal static string ValidationVisitor_ExceededMaxDepthFix + { + get => GetString("ValidationVisitor_ExceededMaxDepthFix"); + } + + /// + /// This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type. + /// + internal static string FormatValidationVisitor_ExceededMaxDepthFix(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxDepthFix"), p0, p1); + + /// + /// {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'. + /// + internal static string ValidationVisitor_ExceededMaxPropertyDepth + { + get => GetString("ValidationVisitor_ExceededMaxPropertyDepth"); + } + + /// + /// {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'. + /// + internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxPropertyDepth"), p0, p1, p2, p3); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 4b31a83761..4916783a8c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -457,4 +457,13 @@ A method named '{0}' was not found on convention type '{1}'. + + {0} exceeded the maximum configured validation depth '{1}' when validating type '{2}'. + + + This may indicate a very deep or infinitely recursive object graph. Consider modifying '{0}.{1}' or suppressing validation on the model type. + + + {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'. + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs index d46f73d642..d26aa506c7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs @@ -2711,7 +2711,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test var controller = GetController(binder, valueProvider: null); controller.ObjectValidator = new DefaultObjectValidator( controller.MetadataProvider, - new[] { Mock.Of() }); + new[] { Mock.Of() }, + new MvcOptions()); var model = new TryValidateModelModel(); @@ -2747,7 +2748,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test var controller = GetController(binder, valueProvider: null); controller.ObjectValidator = new DefaultObjectValidator( controller.MetadataProvider, - new[] { provider.Object }); + new[] { provider.Object }, + new MvcOptions()); // Act var result = controller.TryValidateModel(model, "Prefix"); @@ -2783,7 +2785,8 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test var controller = GetController(binder, valueProvider: null); controller.ObjectValidator = new DefaultObjectValidator( controller.MetadataProvider, - new[] { provider.Object }); + new[] { provider.Object }, + new MvcOptions()); // Act var result = controller.TryValidateModel(model); @@ -2867,7 +2870,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test ControllerContext = controllerContext, MetadataProvider = metadataProvider, ModelBinderFactory = binderFactory.Object, - ObjectValidator = new DefaultObjectValidator(metadataProvider, validatorProviders), + ObjectValidator = new DefaultObjectValidator(metadataProvider, validatorProviders, new MvcOptions()), }; return controller; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs index 90305c7a08..2e72325feb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs @@ -135,7 +135,7 @@ namespace Microsoft.AspNetCore.Mvc.Controllers .Returns(metadataProvider); services .Setup(s => s.GetService(typeof(IObjectModelValidator))) - .Returns(new DefaultObjectValidator(metadataProvider, new List())); + .Returns(new DefaultObjectValidator(metadataProvider, new List(), new MvcOptions())); return services.Object; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs index 436693bbb9..3a705e334e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs @@ -215,7 +215,8 @@ namespace Microsoft.AspNetCore.Mvc.Controllers .Setup(s => s.GetService(typeof(IObjectModelValidator))) .Returns(new DefaultObjectValidator( metadataProvider, - TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders)); + TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders, + new MvcOptions())); return services.Object; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs new file mode 100644 index 0000000000..d661fe0bd5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs @@ -0,0 +1,83 @@ +// 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.Infrastructure; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Core.Infrastructure +{ + public class MvcOptionsConfigureCompatibilityOptionsTest + { + [Fact] + public void PostConfigure_ConfiguresMaxValidationDepth() + { + // Arrange + var mvcOptions = new MvcOptions(); + var mvcCompatibilityOptions = new MvcCompatibilityOptions + { + CompatibilityVersion = CompatibilityVersion.Version_2_2, + }; + + var configureOptions = new MvcOptionsConfigureCompatibilityOptions( + NullLoggerFactory.Instance, + Options.Create(mvcCompatibilityOptions)); + + // Act + configureOptions.PostConfigure(string.Empty, mvcOptions); + + // Assert + Assert.Equal(32, mvcOptions.MaxValidationDepth); + } + + [Fact] + public void PostConfigure_DoesNotConfiguresMaxValidationDepth_WhenSetToNull() + { + // Arrange + var mvcOptions = new MvcOptions + { + MaxValidationDepth = null, + }; + var mvcCompatibilityOptions = new MvcCompatibilityOptions + { + CompatibilityVersion = CompatibilityVersion.Version_2_2, + }; + + var configureOptions = new MvcOptionsConfigureCompatibilityOptions( + NullLoggerFactory.Instance, + Options.Create(mvcCompatibilityOptions)); + + // Act + configureOptions.PostConfigure(string.Empty, mvcOptions); + + // Assert + Assert.Null(mvcOptions.MaxValidationDepth); + } + + [Fact] + public void PostConfigure_DoesNotConfiguresMaxValidationDepth_WhenSetToValue() + { + // Arrange + var expected = 13; + var mvcOptions = new MvcOptions + { + MaxValidationDepth = expected, + }; + var mvcCompatibilityOptions = new MvcCompatibilityOptions + { + CompatibilityVersion = CompatibilityVersion.Version_2_2, + }; + + var configureOptions = new MvcOptionsConfigureCompatibilityOptions( + NullLoggerFactory.Instance, + Options.Create(mvcCompatibilityOptions)); + + // Act + configureOptions.PostConfigure(string.Empty, mvcOptions); + + // Assert + Assert.Equal(expected, mvcOptions.MaxValidationDepth); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs new file mode 100644 index 0000000000..bb5f1de6ae --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs @@ -0,0 +1,77 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class NullableCompatibilitySwitchTest + { + [Fact] + public void Constructor_WithName_IsValueSetIsFalse() + { + // Arrange & Act + var @switch = new NullableCompatibilitySwitch("TestProperty"); + + // Assert + Assert.Null(@switch.Value); + Assert.False(@switch.IsValueSet); + } + + [Fact] + public void ValueNonInterface_SettingValueToNull_SetsIsValueSetToTrue() + { + // Arrange + var @switch = new NullableCompatibilitySwitch("TestProperty"); + + // Act + @switch.Value = null; + + // Assert + Assert.Null(@switch.Value); + Assert.True(@switch.IsValueSet); + } + + [Fact] + public void ValueNonInterface_SettingValue_SetsIsValueSetToTrue() + { + // Arrange + var @switch = new NullableCompatibilitySwitch("TestProperty"); + + // Act + @switch.Value = false; + + // Assert + Assert.False(@switch.Value); + Assert.True(@switch.IsValueSet); + } + + [Fact] + public void ValueInterface_SettingValueToNull_SetsIsValueSetToTrue() + { + // Arrange + var @switch = new NullableCompatibilitySwitch("TestProperty"); + + // Act + ((ICompatibilitySwitch)@switch).Value = null; + + // Assert + Assert.Null(@switch.Value); + Assert.True(@switch.IsValueSet); + } + + [Fact] + public void ValueInterface_SettingValue_SetsIsValueSetToTrue() + { + // Arrange + var @switch = new NullableCompatibilitySwitch("TestProperty"); + + // Act + ((ICompatibilitySwitch)@switch).Value = true; + + // Assert + Assert.True(@switch.Value); + Assert.True(@switch.IsValueSet); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs index 6b9b9041db..a666abd913 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs @@ -62,14 +62,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(binder.Object); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var controller = new TestController(); - var parameterBinder = new ParameterBinder( + + var parameterBinder = GetParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), - _optionsAccessor, - NullLoggerFactory.Instance); + GetModelValidatorProvider(mockValidator.Object)); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -115,18 +112,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal var mockValidator = new Mock(MockBehavior.Strict); mockValidator.Setup(o => o.Validate(It.IsAny())); + + var validatorProvider = GetModelValidatorProvider(mockValidator.Object); var factory = GetModelBinderFactory(binder.Object); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var controller = new TestController(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), - _optionsAccessor, - NullLoggerFactory.Instance); + GetModelValidatorProvider(mockValidator.Object)); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -170,14 +165,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(binder.Object); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); var controllerContext = GetControllerContext(actionDescriptor); var controller = new TestController(); @@ -217,14 +207,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(binder.Object); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); var controllerContext = GetControllerContext(actionDescriptor); var controller = new TestController(); @@ -272,14 +257,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(binder.Object); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); var controllerContext = GetControllerContext(actionDescriptor); var controller = new TestController(); @@ -329,14 +309,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal .Setup(p => p.GetMetadataForParameter(ParameterInfos.NoAttributesParameterInfo)) .Returns(modelMetadata.Object); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( mockMetadataProvider.Object, - factory, - new DefaultObjectValidator( - mockMetadataProvider.Object, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -382,14 +357,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal .Setup(p => p.GetMetadataForType(typeof(Person))) .Returns(modelMetadata.Object); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( mockMetadataProvider.Object, - factory, - new DefaultObjectValidator( - mockMetadataProvider.Object, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -431,14 +401,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal .Returns(new[] { new ModelValidationResult("memberName", "some message") }); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), - _optionsAccessor, - NullLoggerFactory.Instance); + GetModelValidatorProvider(mockValidator.Object)); + var controller = new TestController(); var arguments = new Dictionary(StringComparer.Ordinal); @@ -489,9 +456,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var parameterBinder = new ParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), + GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)), Options.Create(mvcOptions), NullLoggerFactory.Instance); var controller = new TestController(); @@ -538,14 +503,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(binder.Object); var controller = new TestController(); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + GetModelValidatorProvider(mockValidator.Object)); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -590,9 +551,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var parameterBinder = new ParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), + GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)), _optionsAccessor, NullLoggerFactory.Instance); @@ -689,9 +648,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var parameterBinder = new ParameterBinder( modelMetadataProvider, factory, - new DefaultObjectValidator( - modelMetadataProvider, - new[] { GetModelValidatorProvider(mockValidator.Object) }), + GetObjectValidator(modelMetadataProvider, GetModelValidatorProvider(mockValidator.Object)), _optionsAccessor, NullLoggerFactory.Instance); @@ -731,14 +688,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory("Hello"); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -776,14 +728,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var expected = new List { "Hello", "World", "!!" }; var factory = GetModelBinderFactory(expected); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -821,14 +768,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var factory = GetModelBinderFactory(binder); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Some non default value. controller.NonNullableProperty = -1; @@ -867,14 +809,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var factory = GetModelBinderFactory(binder); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Some non default value. controller.NullableProperty = -1; @@ -934,14 +871,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var factory = GetModelBinderFactory(binder); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Some non default value. controller.NullableProperty = -1; @@ -1002,14 +934,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var binder = new StubModelBinder(ModelBindingResult.Success(model: null)); var factory = GetModelBinderFactory(binder); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Some non default value. controller.NullableProperty = -1; @@ -1094,14 +1021,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var factory = GetModelBinderFactory(inputValue); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -1174,14 +1096,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal controllerContext.ValueProviderFactories.Add(new SimpleValueProviderFactory()); var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var parameterBinder = new ParameterBinder( + var parameterBinder = GetParameterBinder( modelMetadataProvider, - factory, - new DefaultObjectValidator( - modelMetadataProvider, - new IModelValidatorProvider[] { }), - _optionsAccessor, - NullLoggerFactory.Instance); + factory); // Act var binderDelegate = ControllerBinderDelegateProvider.CreateBinderDelegate( @@ -1278,7 +1195,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var parameterBinder = new Mock( new EmptyModelMetadataProvider(), factory, - new DefaultObjectValidator(modelMetadataProvider, new[] { modelValidatorProvider }), + GetObjectValidator(modelMetadataProvider, modelValidatorProvider), _optionsAccessor, NullLoggerFactory.Instance); parameterBinder.Setup(p => p.BindModelAsync( @@ -1415,16 +1332,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal } private static ParameterBinder GetParameterBinder( - IModelBinderFactory factory = null, - IObjectModelValidator validator = null, IModelMetadataProvider modelMetadataProvider = null, + IModelBinderFactory factory = null, IModelValidatorProvider modelValidatorProvider = null) { - if (validator == null) - { - validator = CreateObjectValidator(); - } - if (factory == null) { factory = TestModelBinderFactory.CreateDefault(); @@ -1436,9 +1347,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } var metadataProvider = modelMetadataProvider ?? TestModelMetadataProvider.CreateDefaultProvider(); - var objectModelValidator = new DefaultObjectValidator( - metadataProvider, - new[] { modelValidatorProvider }); + var objectModelValidator = GetObjectValidator(modelMetadataProvider, modelValidatorProvider); return new ParameterBinder( metadataProvider, @@ -1448,16 +1357,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal NullLoggerFactory.Instance); } - private static IObjectModelValidator CreateObjectValidator() + private static DefaultObjectValidator GetObjectValidator( + IModelMetadataProvider modelMetadataProvider, + IModelValidatorProvider validatorProvider) { - var mockValidator = new Mock(MockBehavior.Strict); - mockValidator - .Setup(o => o.Validate( - It.IsAny(), - It.IsAny(), - It.IsAny(), - It.IsAny())); - return mockValidator.Object; + return new DefaultObjectValidator( + modelMetadataProvider, + new[] { validatorProvider }, + _options); } // No need for bind-related attributes on properties in this controller class. Properties are added directly diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs index a9ee38cfc6..fad6540560 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.IO; using System.Linq; +using System.Reflection; using System.Text; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; @@ -22,7 +23,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class DefaultObjectValidatorTests { - private IModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider(); + private readonly MvcOptions _options = new MvcOptions(); + + private ModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider(); [Fact] public void Validate_SimpleValueType_Valid_WithPrefix() @@ -1258,6 +1261,70 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void Validate_Throws_IfValidationDepthExceedsMaxDepth() + { + // Arrange + var maxDepth = 5; + var expected = $"ValidationVisitor exceeded the maximum configured validation depth '{maxDepth}' when validating property '{nameof(DepthObject.Depth)}' on type '{typeof(DepthObject)}'. " + + "This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type."; + _options.MaxValidationDepth = maxDepth; + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new DepthObject(maxDepth); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + + // Act & Assert + var ex = Assert.Throws(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Validate_WorksIfObjectGraphIsSmallerThanMaxDepth() + { + // Arrange + var maxDepth = 5; + _options.MaxValidationDepth = maxDepth; + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new DepthObject(maxDepth - 1); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + + // Act & Assert + validator.Validate(actionContext, validationState, prefix: string.Empty, model); + Assert.True(actionContext.ModelState.IsValid); + } + + [Fact] + public void Validate_Throws_WithMaxDepth_1() + { + // Arrange + var maxDepth = 1; + var expected = $"ValidationVisitor exceeded the maximum configured validation depth '{maxDepth}' when validating property '{nameof(DepthObject.Depth)}' on type '{typeof(DepthObject)}'. " + + "This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type."; + _options.MaxValidationDepth = maxDepth; + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new DepthObject(maxDepth + 1); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLeveleMetadataData), BindingFlags.NonPublic | BindingFlags.Instance); + var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]); + + // Act & Assert + var ex = Assert.Throws(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model)); + Assert.Equal(expected, ex.Message); + Assert.NotNull(ex.HelpLink); + } + private static DefaultObjectValidator CreateValidator(Type excludedType) { var excludeFilters = new List(); @@ -1268,14 +1335,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(excludeFilters.ToArray()); var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders; - return new DefaultObjectValidator(metadataProvider, validatorProviders); + return new DefaultObjectValidator(metadataProvider, validatorProviders, new MvcOptions()); } - private static DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers) + private DefaultObjectValidator CreateValidator(params IMetadataDetailsProvider[] providers) { var metadataProvider = TestModelMetadataProvider.CreateDefaultProvider(providers); var validatorProviders = TestModelValidatorProvider.CreateDefaultProvider().ValidatorProviders; - return new DefaultObjectValidator(metadataProvider, validatorProviders); + return new DefaultObjectValidator(metadataProvider, validatorProviders, _options); } private static void AssertKeysEqual(ModelStateDictionary modelState, params string[] keys) @@ -1398,6 +1465,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal void DoSomething(); } + private void Validate_Throws_ForTopLeveleMetadataData(DepthObject depthObject) { } + // Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member // names are indexers. An example scenario is an attribute that confirms all entries in a list are unique. private class InvalidItemsAttribute : ValidationAttribute @@ -1442,5 +1511,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public string City { get; set; } } + + private class DepthObject + { + public DepthObject(int maxAllowedDepth, int depth = 0) + { + MaxAllowedDepth = maxAllowedDepth; + Depth = depth; + } + + public int Depth { get; } + public int MaxAllowedDepth { get; } + + public DepthObject Instance + { + get + { + if (Depth == MaxAllowedDepth - 1) + { + return this; + } + + return new DepthObject(MaxAllowedDepth, Depth + 1); + } + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs index 062b5fddce..116224e517 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator })); + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions())); // Assert Assert.False(result); @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator })); + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions())); // Assert Assert.True(result); @@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator }), + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()), propertyFilter); // Assert @@ -278,7 +278,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding TestModelMetadataProvider.CreateDefaultProvider(), GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator }), + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()), m => m.IncludedProperty, m => m.MyProperty); @@ -329,7 +329,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator })); + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions())); // Assert // Includes everything. @@ -531,7 +531,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator }), + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()), propertyFilter); // Assert @@ -599,7 +599,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding metadataProvider, GetModelBinderFactory(binderProviders), valueProvider, - new DefaultObjectValidator(metadataProvider, new[] { validator })); + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions())); // Assert Assert.True(result); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs index d82f37de92..9464e6def7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs @@ -522,7 +522,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -576,7 +577,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -630,7 +632,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -683,7 +686,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -741,7 +745,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -802,7 +807,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -868,7 +874,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding Mock.Of(), new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -947,7 +954,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding mockModelBinderFactory.Object, new DefaultObjectValidator( mockModelMetadataProvider.Object, - new[] { GetModelValidatorProvider(validator) }), + new[] { GetModelValidatorProvider(validator) }, + new MvcOptions()), optionsAccessor, loggerFactory ?? NullLoggerFactory.Instance); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs index 05e12e9f64..138e70f6c8 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -1,11 +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; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Text; using System.Threading.Tasks; +using FormatterWebSite; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Testing.xunit; using Newtonsoft.Json; @@ -209,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange var invalidRequestData = "{\"FirstName\":\"Test\", \"LastName\": \"Testsson\"}"; var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); - var expectedErrorMessage = + var expectedErrorMessage = "{\"LastName\":[\"The field LastName must be a string with a maximum length of 5.\"]}"; // Act @@ -229,7 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange var invalidRequestData = "{\"FirstName\":\"Testname\", \"LastName\": \"\"}"; var content = new StringContent(invalidRequestData, Encoding.UTF8, "application/json"); - var expectedError = + var expectedError = "{\"LastName\":[\"The LastName field is required.\"]," + "\"FirstName\":[\"The field FirstName must be a string with a maximum length of 5.\"]}"; @@ -243,5 +245,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var responseContent = await response.Content.ReadAsStringAsync(); Assert.Equal(expectedError, actual: responseContent); } + + // Test for https://github.com/aspnet/Mvc/issues/7357 + [Fact] + public async Task ValidationThrowsError_WhenValidationExceedsMaxValidationDepth() + { + // Arrange + var expected = $"ValidationVisitor exceeded the maximum configured validation depth '32' when validating property 'Value' on type '{typeof(RecursiveIdentifier)}'. " + + "This may indicate a very deep or infinitely recursive object graph. Consider modifying 'MvcOptions.MaxValidationDepth' or suppressing validation on the model type."; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "Validation/ValidationThrowsError_WhenValidationExceedsMaxValidationDepth") + { + Content = new StringContent(@"{ ""Id"": ""S-1-5-21-1004336348-1177238915-682003330-512"" }", Encoding.UTF8, "application/json"), + }; + + // Act & Assert + var ex = await Assert.ThrowsAsync(() => Client.SendAsync(requestMessage)); + Assert.Equal(expected, ex.Message); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs index b3686104a8..91a290406a 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -9,7 +9,6 @@ using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Routing; @@ -79,7 +78,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests new ModelBinderFactory(metadataProvider, options, serviceProvider), new DefaultObjectValidator( metadataProvider, - new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }), + new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }, + options.Value), options, NullLoggerFactory.Instance); } @@ -102,7 +102,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests new ModelBinderFactory(metadataProvider, options, services), new DefaultObjectValidator( metadataProvider, - new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }), + new[] { new CompositeModelValidatorProvider(GetModelValidatorProviders(options)) }, + options.Value), options, NullLoggerFactory.Instance); } @@ -127,7 +128,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests { return new DefaultObjectValidator( metadataProvider, - GetModelValidatorProviders(options)); + GetModelValidatorProviders(options), + options?.Value ?? new MvcOptions()); } private static IList GetModelValidatorProviders(IOptions options) diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs index 5b7899ac3e..45078ef126 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs @@ -1555,7 +1555,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal return new ParameterBinder( metadataProvider, factory, - new DefaultObjectValidator(metadataProvider, new[] { validator }), + new DefaultObjectValidator(metadataProvider, new[] { validator }, new MvcOptions()), Options.Create(mvcOptions), NullLoggerFactory.Instance); } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs index eed043db10..6c983e1138 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs @@ -553,7 +553,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal modelBinderFactory, new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -678,7 +679,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal modelBinderFactory, new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), _optionsAccessor, NullLoggerFactory.Instance); @@ -732,7 +734,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal modelBinderFactory, new DefaultObjectValidator( modelMetadataProvider, - new[] { TestModelValidatorProvider.CreateDefaultProvider() }), + new[] { TestModelValidatorProvider.CreateDefaultProvider() }, + new MvcOptions()), Options.Create(mvcOptions), NullLoggerFactory.Instance); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 858dcdf26d..f5a19379d0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -40,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(jsonOptions.AllowInputFormatterExceptionMessages); Assert.False(razorPagesOptions.AllowAreas); Assert.False(mvcOptions.EnableEndpointRouting); + Assert.Null(mvcOptions.MaxValidationDepth); } [Fact] @@ -65,6 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); Assert.False(mvcOptions.EnableEndpointRouting); + Assert.Null(mvcOptions.MaxValidationDepth); } [Fact] @@ -90,6 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); Assert.True(mvcOptions.EnableEndpointRouting); + Assert.Equal(32, mvcOptions.MaxValidationDepth); } [Fact] @@ -115,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(jsonOptions.AllowInputFormatterExceptionMessages); Assert.True(razorPagesOptions.AllowAreas); Assert.True(mvcOptions.EnableEndpointRouting); + Assert.Equal(32, mvcOptions.MaxValidationDepth); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs index d88b338f7c..6abab5d7a5 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs @@ -458,7 +458,7 @@ namespace Microsoft.AspNetCore.Mvc.Test { ControllerContext = controllerContext, MetadataProvider = metadataProvider, - ObjectValidator = new DefaultObjectValidator(metadataProvider, valiatorProviders), + ObjectValidator = new DefaultObjectValidator(metadataProvider, valiatorProviders, new MvcOptions()), TempData = tempData, ViewData = viewData, }; diff --git a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs index 9f2bd1b536..5aee43c803 100644 --- a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs @@ -78,5 +78,11 @@ namespace FormatterWebSite return Ok(); } + + [HttpPost] + public IActionResult ValidationThrowsError_WhenValidationExceedsMaxValidationDepth([FromBody] InfinitelyRecursiveModel model) + { + return Ok(); + } } } \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs b/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs new file mode 100644 index 0000000000..370151143e --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs @@ -0,0 +1,29 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Newtonsoft.Json; + +namespace FormatterWebSite +{ + public class InfinitelyRecursiveModel + { + [JsonConverter(typeof(StringIdentifierConverter))] + public RecursiveIdentifier Id { get; set; } + + private class StringIdentifierConverter : JsonConverter + { + public override bool CanConvert(Type objectType) => objectType == typeof(RecursiveIdentifier); + + public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) + { + return new RecursiveIdentifier(reader.Value.ToString()); + } + + public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) + { + throw new NotImplementedException(); + } + } + } +} diff --git a/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs b/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs new file mode 100644 index 0000000000..847a01b428 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs @@ -0,0 +1,18 @@ +// 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 FormatterWebSite +{ + // A System.Security.Principal.SecurityIdentifier like type that works on xplat + public class RecursiveIdentifier + { + public RecursiveIdentifier(string identifier) + { + Value = identifier; + } + + public string Value { get; } + + public RecursiveIdentifier AccountIdentifier => new RecursiveIdentifier(Value); + } +} \ No newline at end of file diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/test/WebSites/FormatterWebSite/Startup.cs index ee2744a028..c1b5895cea 100644 --- a/test/WebSites/FormatterWebSite/Startup.cs +++ b/test/WebSites/FormatterWebSite/Startup.cs @@ -19,12 +19,12 @@ namespace FormatterWebSite options.InputFormatters.Add(new StringInputFormatter()); }) - .AddXmlDataContractSerializerFormatters(); + .AddXmlDataContractSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.Configure(options => { options.SerializerSettings.Converters.Insert(0, new IModelConverter()); }); } - public void Configure(IApplicationBuilder app) { app.UseMvc(routes => From ea681786bd4fade1f8a76910ae979b66102e1dd9 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 21 Aug 2018 13:33:51 -0700 Subject: [PATCH 182/316] Update package branding for 2.2.0-preview2 --- build/dependencies.props | 4 ++-- version.props | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 7fbb2a958b..e1fbf0322d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -108,6 +108,6 @@ 2.3.1 2.4.0 - + diff --git a/version.props b/version.props index 4d7257694d..d623e08663 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@  2.2.0 - preview1 + preview2 t000 a- @@ -11,7 +11,7 @@ $(VersionSuffix)-$(BuildNumber) 0.2.0 - preview1 + preview2 $(ExperimentalVersionPrefix) $(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final From f624c9aa7b3735f613ae059a26fe180f4155c89c Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 20 Aug 2018 12:01:09 -0700 Subject: [PATCH 183/316] Add flaky test logging for `CacheTagHelper_VaryByCultureComposesWithOtherVaryByOptions`. #8281 --- .../HtmlGenerationWithCultureTest.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs index 9b1d1f5cbe..d028f5ec24 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs @@ -6,9 +6,12 @@ using System.Net.Http; using System.Threading.Tasks; using AngleSharp.Dom; using AngleSharp.Dom.Html; +using AngleSharp.Extensions; +using AngleSharp.Html; using HtmlGenerationWebSite; using Microsoft.AspNetCore.Hosting; using Xunit; +using Xunit.Sdk; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { @@ -149,6 +152,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert - 3 Assert.Equal("fr-FR", culture); Assert.Equal("14", correlationId); + + if (cachedCorrelationId != "10") + { + // This is logging to investigate potential flakiness in this test tracked by https://github.com/aspnet/Mvc/issues/8281 + var documentContent = document.ToHtml(new HtmlMarkupFormatter()); + throw new XunitException($"Unexpected correlation Id, reading values from document:{Environment.NewLine}{documentContent}"); + } + Assert.Equal("10", cachedCorrelationId); void ReadValuesFromDocument() From 5bd818bd64641208e52e9350f443d1336f544e41 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 7 Aug 2018 15:10:03 -0700 Subject: [PATCH 184/316] Introduce ProblemDetailsFilter Fixes https://github.com/aspnet/Mvc/issues/6786 --- .../ApiBehaviorOptions.cs | 118 +++++++++++++- .../MvcCoreServiceCollectionExtensions.cs | 2 + .../Infrastructure/ClientErrorResultFilter.cs | 53 +++++++ .../IClientErrorActionResult.cs | 12 ++ .../ApiBehaviorApplicationModelProvider.cs | 17 ++ .../Internal/ApiBehaviorOptionsSetup.cs | 147 ++++++++++++++++-- .../Internal/MvcCoreLoggerExtensions.cs | 12 ++ .../MvcOptions.cs | 2 +- .../Properties/Resources.Designer.cs | 112 +++++++++++++ .../Resources.resx | 24 +++ .../StatusCodeResult.cs | 2 +- .../MvcCoreServiceCollectionExtensionsTest.cs | 7 + .../ClientErrorResultFilterTest.cs | 104 +++++++++++++ ...ApiBehaviorApplicationModelProviderTest.cs | 49 +++++- .../Internal/ApiBehaviorOptionsSetupTest.cs | 101 ++++++++++++ .../ApiBehaviorTest.cs | 33 +++- .../CompatibilitySwitchIntegrationTest.cs | 12 ++ .../Controllers/ContactApiController.cs | 9 +- .../{ => Filters}/RequestIdService.cs | 0 test/WebSites/BasicWebSite/Startup.cs | 17 -- ...artupWithCustomInvalidModelStateFactory.cs | 52 +++++++ 21 files changed, 835 insertions(+), 50 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs rename test/WebSites/BasicWebSite/{ => Filters}/RequestIdService.cs (100%) create mode 100644 test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs index e955f25768..54614a2465 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; +using System.Collections.Generic; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc @@ -10,17 +13,31 @@ namespace Microsoft.AspNetCore.Mvc /// /// Options used to configure behavior for types annotated with . /// - public class ApiBehaviorOptions + public class ApiBehaviorOptions : IEnumerable { + private readonly CompatibilitySwitch _suppressUseClientErrorFactory; + private readonly CompatibilitySwitch _suppressUseValidationProblemDetailsForInvalidModelStateResponses; + private readonly ICompatibilitySwitch[] _switches; + private Func _invalidModelStateResponseFactory; + /// + /// Creates a new instance of . + /// + public ApiBehaviorOptions() + { + _suppressUseClientErrorFactory = new CompatibilitySwitch(nameof(SuppressUseClientErrorFactory)); + _suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses)); + _switches = new[] + { + _suppressUseClientErrorFactory, + _suppressUseValidationProblemDetailsForInvalidModelStateResponses, + }; + } + /// /// Delegate invoked on actions annotated with to convert invalid /// into an - /// - /// By default, the delegate produces a that wraps a serialized form - /// of . - /// /// public Func InvalidModelStateResponseFactory { @@ -52,5 +69,96 @@ namespace Microsoft.AspNetCore.Mvc /// that are bound from form data. /// public bool SuppressConsumesConstraintForFormFileParameters { get; set; } + + /// + /// Gets or sets a value that determines if controllers with use + /// to transform certain certain client errors. + /// + /// When false, is used to transform to the value + /// specified by the factory. In the default case, this converts instances to an + /// with . + /// + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless explicitly configured. + /// + /// + public bool SuppressUseClientErrorFactory + { + // Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property. + get => _suppressUseClientErrorFactory.Value; + set => _suppressUseClientErrorFactory.Value = value; + } + + /// + /// Gets or sets a value that determines if controllers annotated with respond using + /// in . + /// + /// When , returns errors in + /// as a . Otherwise, returns the errors + /// in the format determined by . + /// + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless explicitly configured. + /// + /// + public bool SuppressUseValidationProblemDetailsForInvalidModelStateResponses + { + get => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value; + set => _suppressUseValidationProblemDetailsForInvalidModelStateResponses.Value = value; + } + + /// + /// Gets a map of HTTP status codes to factories. + /// Configured factories are used when is . + /// + public IDictionary> ClientErrorFactory { get; } = + new Dictionary>(); + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_switches).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index e7e7d5e0dd..3338870e73 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -148,6 +148,8 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Transient, MvcOptionsConfigureCompatibilityOptions>()); services.TryAddEnumerable( ServiceDescriptor.Transient, ApiBehaviorOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, ApiBehaviorOptionsSetup>()); services.TryAddEnumerable( ServiceDescriptor.Transient, MvcCoreRouteOptionsSetup>()); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs new file mode 100644 index 0000000000..a6482dfe09 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.Extensions.Logging; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter + { + private readonly IDictionary> _clientErrorFactory; + private readonly ILogger _logger; + + /// + /// Gets the filter order. Defaults to -2000 so that it runs early. + /// + public int Order => -2000; + + public ClientErrorResultFilter( + ApiBehaviorOptions apiBehaviorOptions, + ILogger logger) + { + _clientErrorFactory = apiBehaviorOptions?.ClientErrorFactory ?? throw new ArgumentNullException(nameof(apiBehaviorOptions)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public void OnResultExecuted(ResultExecutedContext context) + { + } + + public void OnResultExecuting(ResultExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.Result is IClientErrorActionResult clientErrorActionResult && + clientErrorActionResult.StatusCode is int statusCode && + _clientErrorFactory.TryGetValue(statusCode, out var factory)) + { + var result = factory(context); + + _logger.TransformingClientError(context.Result.GetType(), result?.GetType(), statusCode); + + context.Result = factory(context); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs new file mode 100644 index 0000000000..6d9926b50c --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs @@ -0,0 +1,12 @@ +// 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.Infrastructure +{ + /// + /// An that can be transformed to a more descriptive client error. + /// + public interface IClientErrorActionResult : IStatusCodeActionResult + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index f5930f40a6..bc1ac18c79 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private readonly ApiBehaviorOptions _apiBehaviorOptions; private readonly IModelMetadataProvider _modelMetadataProvider; private readonly ModelStateInvalidFilter _modelStateInvalidFilter; + private readonly ClientErrorResultFilter _clientErrorResultFilter; private readonly ILogger _logger; public ApiBehaviorApplicationModelProvider( @@ -42,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal _modelStateInvalidFilter = new ModelStateInvalidFilter( apiBehaviorOptions.Value, loggerFactory.CreateLogger()); + + _clientErrorResultFilter = new ClientErrorResultFilter( + _apiBehaviorOptions, + loggerFactory.CreateLogger()); } /// @@ -90,6 +95,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal AddInvalidModelStateFilter(actionModel); + AddClientErrorFilter(actionModel); + InferParameterBindingSources(actionModel); InferParameterModelPrefixes(actionModel); @@ -149,6 +156,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionModel.Filters.Add(_modelStateInvalidFilter); } + private void AddClientErrorFilter(ActionModel actionModel) + { + if (_apiBehaviorOptions.SuppressUseClientErrorFactory) + { + return; + } + + actionModel.Filters.Add(_clientErrorResultFilter); + } + // Internal for unit testing internal void InferParameterBindingSources(ActionModel actionModel) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs index 48f50f7980..15c4ef8892 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs @@ -2,17 +2,44 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Internal { - public class ApiBehaviorOptionsSetup : IConfigureOptions + public class ApiBehaviorOptionsSetup : + ConfigureCompatibilityOptions, + IConfigureOptions { + internal static readonly Func DefaultFactory = DefaultInvalidModelStateResponse; + internal static readonly Func ProblemDetailsFactory = ProblemDetailsInvalidModelStateResponse; - public ApiBehaviorOptionsSetup() + public ApiBehaviorOptionsSetup( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) { } + protected override IReadOnlyDictionary DefaultValues + { + get + { + var dictionary = new Dictionary(); + + if (Version < CompatibilityVersion.Version_2_2) + { + dictionary[nameof(ApiBehaviorOptions.SuppressUseClientErrorFactory)] = true; + dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true; + } + + return dictionary; + } + } + public void Configure(ApiBehaviorOptions options) { if (options == null) @@ -20,17 +47,117 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(options)); } - options.InvalidModelStateResponseFactory = GetInvalidModelStateResponse; + options.InvalidModelStateResponseFactory = DefaultFactory; + ConfigureClientErrorFactories(options); + } - IActionResult GetInvalidModelStateResponse(ActionContext context) + public override void PostConfigure(string name, ApiBehaviorOptions options) + { + // Let compatibility switches do their thing. + base.PostConfigure(name, options); + + // We want to use problem details factory only if + // (a) it has not been opted out of (SuppressUseClientErrorFactory = true) + // (b) a different factory was configured + if (!options.SuppressUseClientErrorFactory && + object.ReferenceEquals(options.InvalidModelStateResponseFactory, DefaultFactory)) { - var result = new BadRequestObjectResult(context.ModelState); - - result.ContentTypes.Add("application/json"); - result.ContentTypes.Add("application/xml"); - - return result; + options.InvalidModelStateResponseFactory = ProblemDetailsFactory; } } + + // Internal for unit testing + internal static void ConfigureClientErrorFactories(ApiBehaviorOptions options) + { + AddClientErrorFactory(new ProblemDetails + { + Status = 400, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1", + Title = Resources.ApiConventions_Title_400, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 401, + Type = "https://tools.ietf.org/html/rfc7235#section-3.1", + Title = Resources.ApiConventions_Title_401, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 403, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3", + Title = Resources.ApiConventions_Title_403, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 404, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4", + Title = Resources.ApiConventions_Title_404, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 406, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.6", + Title = Resources.ApiConventions_Title_406, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 409, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8", + Title = Resources.ApiConventions_Title_409, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 415, + Type = "https://tools.ietf.org/html/rfc7231#section-6.5.13", + Title = Resources.ApiConventions_Title_415, + }); + + AddClientErrorFactory(new ProblemDetails + { + Status = 422, + Type = "https://tools.ietf.org/html/rfc4918#section-11.2", + Title = Resources.ApiConventions_Title_422, + }); + + void AddClientErrorFactory(ProblemDetails problemDetails) + { + var statusCode = problemDetails.Status.Value; + options.ClientErrorFactory[statusCode] = _ => new ObjectResult(problemDetails) + { + StatusCode = statusCode, + ContentTypes = + { + "application/problem+json", + "application/problem+xml", + }, + }; + } + } + + private static IActionResult DefaultInvalidModelStateResponse(ActionContext context) + { + var result = new BadRequestObjectResult(context.ModelState); + + result.ContentTypes.Add("application/json"); + result.ContentTypes.Add("application/xml"); + + return result; + } + + private static IActionResult ProblemDetailsInvalidModelStateResponse(ActionContext context) + { + var result = new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState)); + + result.ContentTypes.Add("application/problem+json"); + result.ContentTypes.Add("application/problem+xml"); + + return result; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index db626eb4a5..4c2d80972e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -151,6 +151,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static readonly Action _notMostEffectiveFilter; private static readonly Action, Exception> _registeredOutputFormatters; + private static readonly Action _transformingClientError; + static MvcCoreLoggerExtensions() { _actionExecuting = LoggerMessage.Define( @@ -648,6 +650,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal LogLevel.Debug, 48, "Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request."); + + _transformingClientError = LoggerMessage.Define( + LogLevel.Trace, + new EventId(49, nameof(Infrastructure.ClientErrorResultFilter)), + "Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType} produced from ClientErrorFactory'."); } public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable outputFormatters) @@ -1578,6 +1585,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int statusCode) + { + _transformingClientError(logger, initialType, replacedType, statusCode, null); + } + private static void LogFilterExecutionPlan( ILogger logger, string filterType, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index 34a55b4ce4..dd8e716d18 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -95,7 +95,7 @@ namespace Microsoft.AspNetCore.Mvc /// lower then this setting will have the value unless explicitly configured. /// /// - /// If the application's compatibility version is set to or + /// If the application's compatibility version is set to or /// higher then this setting will have the value unless explicitly configured. /// /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 354ce64258..d70a92f526 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1578,6 +1578,118 @@ namespace Microsoft.AspNetCore.Mvc.Core internal static string FormatValidationVisitor_ExceededMaxPropertyDepth(object p0, object p1, object p2, object p3) => string.Format(CultureInfo.CurrentCulture, GetString("ValidationVisitor_ExceededMaxPropertyDepth"), p0, p1, p2, p3); + /// + /// Bad Request + /// + internal static string ApiConventions_Title_400 + { + get => GetString("ApiConventions_Title_400"); + } + + /// + /// Bad Request + /// + internal static string FormatApiConventions_Title_400() + => GetString("ApiConventions_Title_400"); + + /// + /// Unauthorized + /// + internal static string ApiConventions_Title_401 + { + get => GetString("ApiConventions_Title_401"); + } + + /// + /// Unauthorized + /// + internal static string FormatApiConventions_Title_401() + => GetString("ApiConventions_Title_401"); + + /// + /// Forbidden + /// + internal static string ApiConventions_Title_403 + { + get => GetString("ApiConventions_Title_403"); + } + + /// + /// Forbidden + /// + internal static string FormatApiConventions_Title_403() + => GetString("ApiConventions_Title_403"); + + /// + /// Not Found + /// + internal static string ApiConventions_Title_404 + { + get => GetString("ApiConventions_Title_404"); + } + + /// + /// Not Found + /// + internal static string FormatApiConventions_Title_404() + => GetString("ApiConventions_Title_404"); + + /// + /// Not Acceptable + /// + internal static string ApiConventions_Title_406 + { + get => GetString("ApiConventions_Title_406"); + } + + /// + /// Not Acceptable + /// + internal static string FormatApiConventions_Title_406() + => GetString("ApiConventions_Title_406"); + + /// + /// Conflict + /// + internal static string ApiConventions_Title_409 + { + get => GetString("ApiConventions_Title_409"); + } + + /// + /// Conflict + /// + internal static string FormatApiConventions_Title_409() + => GetString("ApiConventions_Title_409"); + + /// + /// Unsupported Media Type + /// + internal static string ApiConventions_Title_415 + { + get => GetString("ApiConventions_Title_415"); + } + + /// + /// Unsupported Media Type + /// + internal static string FormatApiConventions_Title_415() + => GetString("ApiConventions_Title_415"); + + /// + /// Unprocessable Entity + /// + internal static string ApiConventions_Title_422 + { + get => GetString("ApiConventions_Title_422"); + } + + /// + /// Unprocessable Entity + /// + internal static string FormatApiConventions_Title_422() + => GetString("ApiConventions_Title_422"); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 4916783a8c..cc88759296 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -466,4 +466,28 @@ {0} exceeded the maximum configured validation depth '{1}' when validating property '{2}' on type '{3}'. + + Bad Request + + + Unauthorized + + + Forbidden + + + Not Found + + + Not Acceptable + + + Conflict + + + Unsupported Media Type + + + Unprocessable Entity + \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs index 305d279372..c3ec5614c0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs @@ -13,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc /// Represents an that when executed will /// produce an HTTP response with the given response status code. /// - public class StatusCodeResult : ActionResult, IStatusCodeActionResult + public class StatusCodeResult : ActionResult, IClientErrorActionResult { /// /// Initializes a new instance of the class diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index f7223852de..ba70d85c6e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -264,6 +264,13 @@ namespace Microsoft.AspNetCore.Mvc typeof(ApiBehaviorOptionsSetup), } }, + { + typeof(IPostConfigureOptions), + new Type[] + { + typeof(ApiBehaviorOptionsSetup), + } + }, { typeof(IActionConstraintProvider), new Type[] diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs new file mode 100644 index 0000000000..7c3f55858d --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Logging.Abstractions; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class ClientErrorResultFilterTest + { + private static readonly IActionResult Result = new EmptyResult(); + + [Fact] + public void OnResultExecuting_DoesNothing_IfActionIsNotClientErrorActionResult() + { + // Arrange + var actionResult = new NotFoundObjectResult(new object()); + var context = GetContext(actionResult); + var filter = GetFilter(); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(actionResult, context.Result); + } + + [Fact] + public void OnResultExecuting_DoesNothing_IfStatusCodeDoesNotExistInApiBehaviorOptions() + { + // Arrange + var actionResult = new NotFoundResult(); + var context = GetContext(actionResult); + var filter = GetFilter(new ApiBehaviorOptions()); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(actionResult, context.Result); + } + + [Fact] + public void OnResultExecuting_DoesNothing_IfResultDoesNotHaveStatusCode() + { + // Arrange + var actionResult = new Mock() + .As() + .Object; + var context = GetContext(actionResult); + var filter = GetFilter(new ApiBehaviorOptions()); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(actionResult, context.Result); + } + + [Fact] + public void OnResultExecuting_TransformsClientErrors() + { + // Arrange + var actionResult = new NotFoundResult(); + var context = GetContext(actionResult); + var filter = GetFilter(); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(Result, context.Result); + } + + private static ClientErrorResultFilter GetFilter(ApiBehaviorOptions options = null) + { + var apiBehaviorOptions = options ?? GetOptions(); + var filter = new ClientErrorResultFilter(apiBehaviorOptions, NullLogger.Instance); + return filter; + } + + private static ApiBehaviorOptions GetOptions() + { + var apiBehaviorOptions = new ApiBehaviorOptions(); + apiBehaviorOptions.ClientErrorFactory[404] = _ => Result; + return apiBehaviorOptions; + } + + private static ResultExecutingContext GetContext(IActionResult actionResult) + { + return new ResultExecutingContext( + new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()), + Array.Empty(), + actionResult, + new object()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index fc47f2abd0..3b5d4415de 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); - Assert.IsType(actionModel.Filters.Last()); + Assert.Single(actionModel.Filters.OfType()); } [Fact] @@ -54,8 +54,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal provider.OnProvidersExecuting(context); // Assert - var controllerModel = Assert.Single(context.Result.Controllers); - Assert.DoesNotContain(typeof(ModelStateInvalidFilter), controllerModel.Filters.Select(f => f.GetType())); + var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); + Assert.Empty(actionModel.Filters.OfType()); } [Fact] @@ -73,11 +73,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName), action => { - Assert.Contains(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType())); + Assert.Single(action.Filters.OfType()); }, action => { - Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType())); + Assert.Empty(action.Filters.OfType()); }); } @@ -101,11 +101,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName), action => { - Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType())); + Assert.Empty(action.Filters.OfType()); }, action => { - Assert.DoesNotContain(typeof(ModelStateInvalidFilter), action.Filters.Select(f => f.GetType())); + Assert.Empty(action.Filters.OfType()); }); } @@ -1059,6 +1059,41 @@ Environment.NewLine + "int b"; }); } + [Fact] + public void OnProvidersExecuting_AddsClientErrorResultFilter() + { + // Arrange + var context = GetContext(typeof(TestApiController)); + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); + Assert.Single(actionModel.Filters.OfType()); + } + + [Fact] + public void OnProvidersExecuting_DoesNotAddClientErrorResultFilter_IfFeatureIsDisabled() + { + // Arrange + var context = GetContext(typeof(TestApiController)); + var options = new ApiBehaviorOptions + { + SuppressUseClientErrorFactory = true, + InvalidModelStateResponseFactory = _ => null, + }; + var provider = GetProvider(options); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); + Assert.Empty(actionModel.Filters.OfType()); + } + // A dynamically generated type in an assembly that has an ApiConventionAttribute. private static TypeBuilder CreateTestControllerType() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs new file mode 100644 index 0000000000..66f4f7933b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + public class ApiBehaviorOptionsSetupTest + { + [Fact] + public void Configure_AssignsInvalidModelStateResponseFactory() + { + // Arrange + var optionsSetup = new ApiBehaviorOptionsSetup( + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions())); + var options = new ApiBehaviorOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.Same(ApiBehaviorOptionsSetup.DefaultFactory, options.InvalidModelStateResponseFactory); + } + + [Fact] + public void Configure_AddsClientErrorFactories() + { + // Arrange + var expected = new[] { 400, 401, 403, 404, 406, 409, 415, 422, }; + var optionsSetup = new ApiBehaviorOptionsSetup( + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions())); + var options = new ApiBehaviorOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.Equal(expected, options.ClientErrorFactory.Keys); + } + + [Fact] + public void PostConfigure_SetProblemDetailsModelStateResponseFactory() + { + // Arrange + var optionsSetup = new ApiBehaviorOptionsSetup( + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest })); + var options = new ApiBehaviorOptions(); + + // Act + optionsSetup.Configure(options); + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.Same(ApiBehaviorOptionsSetup.ProblemDetailsFactory, options.InvalidModelStateResponseFactory); + } + + [Fact] + public void PostConfigure_DoesNotSetProblemDetailsFactoryWithLegacyCompatBehavior() + { + // Arrange + var optionsSetup = new ApiBehaviorOptionsSetup( + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_1 })); + var options = new ApiBehaviorOptions(); + + // Act + optionsSetup.Configure(options); + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.Same(ApiBehaviorOptionsSetup.DefaultFactory, options.InvalidModelStateResponseFactory); + } + + [Fact] + public void PostConfigure_DoesNotSetProblemDetailsFactory_IfValueWasModified() + { + // Arrange + var optionsSetup = new ApiBehaviorOptionsSetup( + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Latest })); + var options = new ApiBehaviorOptions(); + Func expected = _ => null; + + // Act + optionsSetup.Configure(options); + // This is equivalent to user code updating the value via ConfigureOptions + options.InvalidModelStateResponseFactory = expected; + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.Same(expected, options.InvalidModelStateResponseFactory); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index cfb5df0887..0d0a85999f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -8,6 +8,7 @@ using System.Net.Http; using System.Text; using System.Threading.Tasks; using BasicWebSite.Models; +using Microsoft.AspNetCore.Hosting; using Newtonsoft.Json; using Xunit; @@ -18,9 +19,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public ApiBehaviorTest(MvcTestFixture fixture) { Client = fixture.CreateDefaultClient(); + + var factory = fixture.WithWebHostBuilder(ConfigureWebHostBuilder); + CustomInvalidModelStateClient = factory.CreateDefaultClient(); } + private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => + builder.UseStartup(); + public HttpClient Client { get; } + public HttpClient CustomInvalidModelStateClient { get; } [Fact] public async Task ActionsReturnBadRequest_WhenModelStateIsInvalid() @@ -39,11 +47,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var response = await Client.PostAsJsonAsync("/contact", contactModel); // Assert - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); - Assert.Equal("application/json", response.Content.Headers.ContentType.MediaType); - var actual = JsonConvert.DeserializeObject>(await response.Content.ReadAsStringAsync()); + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType); + var problemDetails = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); Assert.Collection( - actual.OrderBy(kvp => kvp.Key), + problemDetails.Errors.OrderBy(kvp => kvp.Key), kvp => { Assert.Equal("Name", kvp.Key); @@ -110,10 +118,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var contactString = JsonConvert.SerializeObject(contactModel); // Act - var response = await Client.PostAsJsonAsync("/contact/PostWithVnd", contactModel); + var response = await CustomInvalidModelStateClient.PostAsJsonAsync("/contact/PostWithVnd", contactModel); // Assert - Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); Assert.Equal("application/vnd.error+json", response.Content.Headers.ContentType.MediaType); var content = await response.Content.ReadAsStringAsync(); var actual = JsonConvert.DeserializeObject>(content); @@ -236,5 +244,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var result = await response.Content.ReadAsStringAsync(); Assert.Equal(expected, result); } + + [Fact] + public async Task ClientErrorResultFilterExecutesForStatusCodeResults() + { + // Act + var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + var problemDetails = JsonConvert.DeserializeObject(content); + Assert.Equal(404, problemDetails.Status); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index f5a19379d0..813c148bfe 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -31,6 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var mvcOptions = services.GetRequiredService>().Value; var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; + var apiBehaviorOptions = services.GetRequiredService>().Value; // Assert Assert.False(mvcOptions.AllowCombiningAuthorizeFilters); @@ -41,6 +42,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(razorPagesOptions.AllowAreas); Assert.False(mvcOptions.EnableEndpointRouting); Assert.Null(mvcOptions.MaxValidationDepth); + Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); + Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory); } [Fact] @@ -57,6 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var mvcOptions = services.GetRequiredService>().Value; var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; + var apiBehaviorOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -67,6 +71,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(razorPagesOptions.AllowAreas); Assert.False(mvcOptions.EnableEndpointRouting); Assert.Null(mvcOptions.MaxValidationDepth); + Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); + Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory); } [Fact] @@ -83,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var mvcOptions = services.GetRequiredService>().Value; var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; + var apiBehaviorOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -93,6 +100,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(razorPagesOptions.AllowAreas); Assert.True(mvcOptions.EnableEndpointRouting); Assert.Equal(32, mvcOptions.MaxValidationDepth); + Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); + Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory); } [Fact] @@ -109,6 +118,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var mvcOptions = services.GetRequiredService>().Value; var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; + var apiBehaviorOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -119,6 +129,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(razorPagesOptions.AllowAreas); Assert.True(mvcOptions.EnableEndpointRouting); Assert.Equal(32, mvcOptions.MaxValidationDepth); + Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); + Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs index 77a757f1ff..a2cd22afc5 100644 --- a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs +++ b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs @@ -1,9 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using BasicWebSite.Models; @@ -88,6 +85,12 @@ namespace BasicWebSite return foo; } + [HttpGet("[action]")] + public ActionResult ActionReturningStatusCodeResult() + { + return NotFound(); + } + private class TestModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) diff --git a/test/WebSites/BasicWebSite/RequestIdService.cs b/test/WebSites/BasicWebSite/Filters/RequestIdService.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestIdService.cs rename to test/WebSites/BasicWebSite/Filters/RequestIdService.cs diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 1c3218266e..86d5bab150 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -1,7 +1,6 @@ // 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.Linq; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; @@ -33,22 +32,6 @@ namespace BasicWebSite .SetCompatibilityVersion(CompatibilityVersion.Latest) .AddXmlDataContractSerializerFormatters(); - services.Configure(options => - { - var previous = options.InvalidModelStateResponseFactory; - options.InvalidModelStateResponseFactory = context => - { - var result = (BadRequestObjectResult)previous(context); - if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute)) - { - result.ContentTypes.Clear(); - result.ContentTypes.Add("application/vnd.error+json"); - } - - return result; - }; - }); - services.ConfigureBaseWebSiteAuthPolicies(); services.AddTransient(); diff --git a/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs b/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs new file mode 100644 index 0000000000..27c7b58761 --- /dev/null +++ b/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs @@ -0,0 +1,52 @@ +// 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.Linq; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace BasicWebSite +{ + public class StartupWithCustomInvalidModelStateFactory + { + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + services.AddAuthentication() + .AddScheme("Api", _ => { }); + + services + .AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); + + services.Configure(options => + { + var previous = options.InvalidModelStateResponseFactory; + options.InvalidModelStateResponseFactory = context => + { + var result = (BadRequestObjectResult)previous(context); + if (context.ActionDescriptor.FilterDescriptors.Any(f => f.Filter is VndErrorAttribute)) + { + result.ContentTypes.Clear(); + result.ContentTypes.Add("application/vnd.error+json"); + } + + return result; + }; + }); + + services.ConfigureBaseWebSiteAuthPolicies(); + + services.AddLogging(); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + app.UseMvc(); + } + } +} From cbe152676305cd9a70be5998fcf301ab13d3ccf4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 22 Aug 2018 17:15:09 +1200 Subject: [PATCH 185/316] React to routing changes (#8303) --- build/dependencies.props | 4 +- .../Internal/MvcEndpointDataSource.cs | 14 ++-- .../Routing/ConsumesMatcherPolicy.cs | 4 +- .../Routing/UrlHelperFactory.cs | 1 + .../Internal/MvcEndpointDataSourceTests.cs | 69 ++++++++++--------- .../ActionConstraintMatcherPolicyTest.cs | 7 +- .../Routing/ConsumesMatcherPolicyTest.cs | 7 +- .../Routing/EndpointRoutingUrlHelperTest.cs | 41 +++++------ .../Controllers/RoutingController.cs | 2 +- .../Controllers/RoutingController.cs | 2 +- .../Controllers/RoutingController.cs | 2 +- 11 files changed, 79 insertions(+), 74 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index e1fbf0322d..5e0c447c05 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-a-preview2-matcherendpoint-rename-16892 + 2.2.0-a-preview2-matcherendpoint-rename-16892 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 9e4ff9b92c..5955b3fe04 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -6,11 +6,11 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; @@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return false; } - private MatcherEndpoint CreateEndpoint( + private RouteEndpoint CreateEndpoint( ActionDescriptor action, string routeName, string template, @@ -271,9 +271,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal object source, bool suppressLinkGeneration) { - RequestDelegate invokerDelegate = (context) => + RequestDelegate requestDelegate = (context) => { - var values = context.Features.Get().Values; + var values = context.Features.Get().RouteValues; var routeData = new RouteData(); foreach (var kvp in values) { @@ -299,9 +299,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal source, suppressLinkGeneration); - var endpoint = new MatcherEndpoint( - next => invokerDelegate, - RoutePatternFactory.Parse(template, defaults, constraints: null), + var endpoint = new RouteEndpoint( + requestDelegate, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), order, metadataCollection, action.DisplayName); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs index 3adacf3c38..507d3bcf68 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -135,8 +135,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing private Endpoint CreateRejectionEndpoint() { - return new MatcherEndpoint( - (next) => (context) => + return new RouteEndpoint( + (context) => { context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; return Task.CompletedTask; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs index 933f339f30..dfe16c2421 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs @@ -3,6 +3,7 @@ using System; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index bc8d0dbcdc..ca09424a52 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressMetadata); @@ -77,14 +77,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled() + public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled() { // Arrange - var featureCollection = new FeatureCollection(); - featureCollection.Set(new EndpointFeature + var endpointFeature = new EndpointFeature { - Values = new RouteValueDictionary() - }); + RouteValues = new RouteValueDictionary() + }; + + var featureCollection = new FeatureCollection(); + featureCollection.Set(endpointFeature); + featureCollection.Set(endpointFeature); var httpContextMock = new Mock(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); @@ -122,11 +125,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); - var invokerDelegate = matcherEndpoint.Invoker((next) => Task.CompletedTask); - - invokerDelegate(httpContextMock.Object); + await matcherEndpoint.RequestDelegate(httpContextMock.Object); Assert.True(actionInvokerCalled); } @@ -138,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var featureCollection = new FeatureCollection(); featureCollection.Set(new EndpointFeature { - Values = new RouteValueDictionary() + RouteValues = new RouteValueDictionary() }); var httpContextMock = new Mock(); @@ -199,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -226,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -250,8 +251,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints, - (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); } [Fact] @@ -278,8 +279,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints1, - (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); Assert.Same(endpoints1, endpoints2); actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once); @@ -320,8 +321,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; Assert.Collection(endpoints, - (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), - (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); + (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) @@ -337,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotSame(endpoints, newEndpoints); Assert.Collection(newEndpoints, - (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).RoutePattern.RawText)); + (e) => Assert.Equal("NewTestController/NewTestAction", Assert.IsType(e).RoutePattern.RawText)); } [Fact] @@ -359,8 +360,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints, - (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).RoutePattern.RawText), - (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).RoutePattern.RawText)); + (e) => Assert.Equal("TestController/TestAction1", Assert.IsType(e).RoutePattern.RawText), + (e) => Assert.Equal("TestController/TestAction2", Assert.IsType(e).RoutePattern.RawText)); } [Theory] @@ -383,7 +384,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; var inspectors = finalEndpointTemplates - .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); // Assert @@ -405,7 +406,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressNameMetadata); Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name); @@ -430,7 +431,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpoints, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressMetadata); Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); @@ -438,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal }, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressMetadata); Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); @@ -469,25 +470,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpoints, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(1, matcherEndpoint.Order); }, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(2, matcherEndpoint.Order); }, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); Assert.Equal("Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(1, matcherEndpoint.Order); }, (ep) => { - var matcherEndpoint = Assert.IsType(ep); + var matcherEndpoint = Assert.IsType(ep); Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); Assert.Equal(2, matcherEndpoint.Order); }); @@ -589,7 +590,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } @@ -611,7 +612,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } @@ -634,7 +635,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } @@ -656,7 +657,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); + var matcherEndpoint = Assert.IsType(endpoint); Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 31e3dd8653..a423dfd2e3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; @@ -369,11 +370,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing return httpContext; } - private static MatcherEndpoint CreateEndpoint(ActionDescriptor action) + private static RouteEndpoint CreateEndpoint(ActionDescriptor action) { var metadata = new List() { action, }; - return new MatcherEndpoint( - (r) => null, + return new RouteEndpoint( + (context) => Task.CompletedTask, RoutePatternFactory.Parse("/"), 0, new EndpointMetadataCollection(metadata), diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs index 48a49e465e..cad3edc0b9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; @@ -212,7 +213,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.Equal(expected, actual); } - private static MatcherEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata) + private static RouteEndpoint CreateEndpoint(string template, ConsumesMetadata consumesMetadata) { var metadata = new List(); if (consumesMetadata != null) @@ -220,8 +221,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing metadata.Add(consumesMetadata); } - return new MatcherEndpoint( - (next) => null, + return new RouteEndpoint( + (context) => Task.CompletedTask, RoutePatternFactory.Parse(template), 0, new EndpointMetadataCollection(metadata), diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index e02dd3c5c9..6853dd7180 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -5,6 +5,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Patterns; @@ -60,8 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Set the endpoint feature and current context just as a normal request to MVC app would be var endpointFeature = new EndpointFeature(); urlHelper.ActionContext.HttpContext.Features.Set(endpointFeature); + urlHelper.ActionContext.HttpContext.Features.Set(endpointFeature); endpointFeature.Endpoint = endpoint1; - endpointFeature.Values = new RouteValueDictionary + endpointFeature.RouteValues = new RouteValueDictionary { ["controller"] = "Orders", ["action"] = "GetById", @@ -123,7 +126,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing protected override IUrlHelper CreateUrlHelper(string appRoot, string host, string protocol) { - return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); + return CreateUrlHelper(Enumerable.Empty(), appRoot, host, protocol); } protected override IUrlHelper CreateUrlHelperWithDefaultRoutes(string appRoot, string host, string protocol) @@ -139,8 +142,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing string template) { var endpoints = GetDefaultEndpoints(); - endpoints.Add(new MatcherEndpoint( - next => httpContext => Task.CompletedTask, + endpoints.Add(new RouteEndpoint( + httpContext => Task.CompletedTask, RoutePatternFactory.Parse(template), 0, EndpointMetadataCollection.Empty, @@ -153,10 +156,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = actionContext.HttpContext; httpContext.Features.Set(new EndpointFeature() { - Endpoint = new MatcherEndpoint( - next => cntxt => Task.CompletedTask, - RoutePatternFactory.Parse("/"), - 0, + Endpoint = new Endpoint( + context => Task.CompletedTask, EndpointMetadataCollection.Empty, null) }); @@ -187,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return CreateUrlHelper(actionContext); } - private IUrlHelper CreateUrlHelper(IEnumerable endpoints, ActionContext actionContext = null) + private IUrlHelper CreateUrlHelper(IEnumerable endpoints, ActionContext actionContext = null) { var serviceProvider = CreateServices(endpoints); var httpContext = CreateHttpContext(serviceProvider, null, null, "http"); @@ -196,7 +197,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } private IUrlHelper CreateUrlHelper( - IEnumerable endpoints, + IEnumerable endpoints, string appRoot, string host, string protocol) @@ -207,9 +208,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing return CreateUrlHelper(actionContext); } - private List GetDefaultEndpoints() + private List GetDefaultEndpoints() { - var endpoints = new List(); + var endpoints = new List(); endpoints.Add( CreateEndpoint( "home/newaction/{id}", @@ -278,7 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return endpoints; } - private MatcherEndpoint CreateEndpoint( + private RouteEndpoint CreateEndpoint( string template, object defaults = null, object requiredValues = null, @@ -292,9 +293,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing new RouteValuesAddressMetadata(routeName, new RouteValueDictionary(requiredValues))); } - return new MatcherEndpoint( - next => (httpContext) => Task.CompletedTask, - RoutePatternFactory.Parse(template, defaults, constraints: null), + return new RouteEndpoint( + (httpContext) => Task.CompletedTask, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), order, metadataCollection, null); @@ -315,11 +316,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing return services.BuildServiceProvider(); } - private MatcherEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults) + private RouteEndpoint GetEndpoint(string name, string template, RouteValueDictionary defaults) { - return new MatcherEndpoint( - next => c => Task.CompletedTask, - RoutePatternFactory.Parse(template, defaults, constraints: null), + return new RouteEndpoint( + c => Task.CompletedTask, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), 0, EndpointMetadataCollection.Empty, null); diff --git a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs index afe914f572..e2c416505d 100644 --- a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs +++ b/test/WebSites/BasicWebSite/Controllers/RoutingController.cs @@ -1,8 +1,8 @@ // 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.Http.Features; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; namespace BasicWebSite { diff --git a/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs index 354ff5539a..ba7df5622b 100644 --- a/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs @@ -1,8 +1,8 @@ // 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.Http.Features; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; namespace RoutingWebSite { diff --git a/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs index aad2099d1d..709fb51494 100644 --- a/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs +++ b/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs @@ -1,8 +1,8 @@ // 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.Http.Features; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Routing; namespace VersioningWebSite { From 03da30f3bfcdaf1af83c9605ec20219a533c9cb1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 23 Aug 2018 10:23:29 +1200 Subject: [PATCH 186/316] Replace remaining references to global routing (#8312) --- .../Builder/MvcApplicationBuilderExtensions.cs | 2 +- .../MvcCoreServiceCollectionExtensions.cs | 2 +- .../Internal/MvcEndpointDataSource.cs | 2 +- .../Routing/ActionConstraintMatcherPolicy.cs | 2 +- .../CorsEndpointRoutingTests.cs | 2 +- .../EndpointRoutingTest.cs | 8 ++++---- test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index c54fcb350a..a3d8fd03b0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -118,7 +118,7 @@ namespace Microsoft.AspNetCore.Builder } else { - throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Global Routing."); + throw new InvalidOperationException($"Cannot use '{router.GetType().FullName}' with Endpoint Routing."); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 3338870e73..33d7e6aa93 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -265,7 +265,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddTransient(); // Many per app // - // Global Routing / Endpoints + // Endpoint Routing / Endpoints // services.TryAddEnumerable( ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 5955b3fe04..2f40d07011 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // up the endpoints too. // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. - // This is for scenarios dealing with migrating existing Router based code to Global Routing world. + // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. var conventionalRouteOrder = 0; // Check each of the conventional templates to see if the action would be reachable diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index 090ade0cf1..46d9adb91c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing foundMatchingConstraint = true; // Before we run the constraint, we need to initialize the route values. - // In global routing, the route values are per-endpoint. + // In endpoint routing, the route values are per-endpoint. constraintContext.RouteContext = new RouteContext(httpContext) { RouteData = new RouteData(candidateSet[item.index].Values), diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs index b64655c4d9..44402ff200 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } - [Fact] // This intentionally returns a 405 with global routing + [Fact] // This intentionally returns a 405 with endpoint routing public override async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index 6eb78940f9..81a21798eb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests result.Routers); } - // Global routing exposes HTTP 405s for HTTP method mismatches + // Endpoint routing exposes HTTP 405s for HTTP method mismatches [Fact] public override async Task ConventionalRoutedController_InArea_ActionBlockedByHttpMethod() { @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - // Global routing exposes HTTP 405s for HTTP method mismatches + // Endpoint routing exposes HTTP 405s for HTTP method mismatches [Fact] public override async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() { @@ -111,7 +111,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - // Global routing exposes HTTP 405s for HTTP method mismatches + // Endpoint routing exposes HTTP 405s for HTTP method mismatches [Theory] [MemberData(nameof(AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraintsData))] public override async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - // Global routing exposes HTTP 405s for HTTP method mismatches + // Endpoint routing exposes HTTP 405s for HTTP method mismatches [Theory] [MemberData(nameof(AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheActionData))] public override async Task AttributeRoutedAction_RejectsRequestsWithWrongMethods_InRoutesWithoutExtraTemplateSegmentsOnTheAction( diff --git a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs index fca926df52..3f3356d7ef 100644 --- a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs @@ -15,7 +15,7 @@ namespace BasicWebSite services.AddRouting(); services.AddMvc() - .SetCompatibilityVersion(CompatibilityVersion.Latest) // this compat version enables global routing + .SetCompatibilityVersion(CompatibilityVersion.Latest) // this compat version enables endpoint routing .AddXmlDataContractSerializerFormatters(); services.ConfigureBaseWebSiteAuthPolicies(); From e2de54a92d8254a27f9eefd77e08370c7b17fa5d Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 23 Aug 2018 21:42:42 +1200 Subject: [PATCH 187/316] Update MvcEndpointDataSource to use RoutePattern (#8249) --- .../MvcEndpointDatasourceBenchmark.cs | 12 +- build/dependencies.props | 4 +- .../MvcApplicationBuilderExtensions.cs | 6 +- .../Builder/MvcEndpointInfo.cs | 83 ++++------- .../Internal/MvcEndpointDataSource.cs | 137 ++++++++++++------ .../Internal/RoutePatternWriter.cs | 75 ++++++++++ .../Internal/RouteTemplateWriter.cs | 56 ------- .../Routing/ActionConstraintMatcherPolicy.cs | 4 +- .../MvcApplicationBuilderExtensionsTest.cs | 2 +- .../Internal/MvcEndpointDataSourceTests.cs | 23 +-- ...terTests.cs => RoutePatternWriterTests.cs} | 12 +- .../ActionConstraintMatcherPolicyTest.cs | 36 ++--- 12 files changed, 248 insertions(+), 202 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/{RouteTemplateWriterTests.cs => RoutePatternWriterTests.cs} (73%) diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs index bb66f15d28..85bf14c1cf 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Mvc.Performance { @@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance new RouteValueDictionary(), new Dictionary(), new RouteValueDictionary(), - new MockInlineConstraintResolver()) + new MockParameterPolicyFactory()) }; } @@ -134,9 +135,14 @@ namespace Microsoft.AspNetCore.Mvc.Performance } } - private class MockInlineConstraintResolver : IInlineConstraintResolver + private class MockParameterPolicyFactory : ParameterPolicyFactory { - public IRouteConstraint ResolveConstraint(string inlineConstraint) + public override IParameterPolicy Create(RoutePatternParameterPart parameter, string inlineText) + { + throw new NotImplementedException(); + } + + public override IParameterPolicy Create(RoutePatternParameterPart parameter, IParameterPolicy parameterPolicy) { throw new NotImplementedException(); } diff --git a/build/dependencies.props b/build/dependencies.props index 5e0c447c05..d6804c2325 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 - 2.2.0-a-preview2-matcherendpoint-rename-16892 - 2.2.0-a-preview2-matcherendpoint-rename-16892 + 2.2.0-a-preview2-routepattern-defaults-16901 + 2.2.0-a-preview2-routepattern-defaults-16901 2.2.0-preview1-34967 2.2.0-preview1-34967 2.2.0-preview1-34967 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs index a3d8fd03b0..06e26ed407 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs @@ -93,8 +93,8 @@ namespace Microsoft.AspNetCore.Builder .GetRequiredService>() .OfType() .First(); - var constraintResolver = app.ApplicationServices - .GetRequiredService(); + var parameterPolicyFactory = app.ApplicationServices + .GetRequiredService(); var endpointRouteBuilder = new EndpointRouteBuilder(app); @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Builder route.Defaults, route.Constraints.ToDictionary(kvp => kvp.Key, kvp => (object)kvp.Value), route.DataTokens, - constraintResolver); + parameterPolicyFactory); mvcEndpointDataSource.ConventionalEndpointInfos.Add(endpointInfo); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index e0943b82c0..8b1eba5fbf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -5,7 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using Microsoft.AspNetCore.Routing; -using Microsoft.AspNetCore.Routing.Template; +using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Builder { @@ -13,35 +13,36 @@ namespace Microsoft.AspNetCore.Builder { public MvcEndpointInfo( string name, - string template, + string pattern, RouteValueDictionary defaults, IDictionary constraints, RouteValueDictionary dataTokens, - IInlineConstraintResolver constraintResolver) + ParameterPolicyFactory parameterPolicyFactory) { Name = name; - Template = template ?? string.Empty; + Pattern = pattern ?? string.Empty; DataTokens = dataTokens; try { - // Data we parse from the template will be used to fill in the rest of the constraints or + // Data we parse from the pattern will be used to fill in the rest of the constraints or // defaults. The parser will throw for invalid routes. - ParsedTemplate = TemplateParser.Parse(template); + ParsedPattern = RoutePatternFactory.Parse(pattern, defaults, constraints); + Constraints = BuildConstraints(parameterPolicyFactory); - Constraints = GetConstraints(constraintResolver, ParsedTemplate, constraints); Defaults = defaults; - MergedDefaults = GetDefaults(ParsedTemplate, defaults); + // Merge defaults outside of RoutePattern because the defaults will already have values from pattern + MergedDefaults = new RouteValueDictionary(ParsedPattern.Defaults); } catch (Exception exception) { throw new RouteCreationException( - string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and template '{1}'.", name, template), exception); + string.Format(CultureInfo.CurrentCulture, "An error occurred while creating the route with name '{0}' and pattern '{1}'.", name, pattern), exception); } } public string Name { get; } - public string Template { get; } + public string Pattern { get; } // Non-inline defaults public RouteValueDictionary Defaults { get; } @@ -49,67 +50,33 @@ namespace Microsoft.AspNetCore.Builder // Inline and non-inline defaults merged into one public RouteValueDictionary MergedDefaults { get; } - public IDictionary Constraints { get; } + public IDictionary> Constraints { get; } public RouteValueDictionary DataTokens { get; } - internal RouteTemplate ParsedTemplate { get; private set; } + public RoutePattern ParsedPattern { get; private set; } - private static IDictionary GetConstraints( - IInlineConstraintResolver inlineConstraintResolver, - RouteTemplate parsedTemplate, - IDictionary constraints) + private Dictionary> BuildConstraints(ParameterPolicyFactory parameterPolicyFactory) { - var constraintBuilder = new RouteConstraintBuilder(inlineConstraintResolver, parsedTemplate.TemplateText); + var constraints = new Dictionary>(StringComparer.OrdinalIgnoreCase); - if (constraints != null) + foreach (var parameter in ParsedPattern.Parameters) { - foreach (var kvp in constraints) + foreach (var parameterPolicy in parameter.ParameterPolicies) { - constraintBuilder.AddConstraint(kvp.Key, kvp.Value); - } - } - - foreach (var parameter in parsedTemplate.Parameters) - { - if (parameter.IsOptional) - { - constraintBuilder.SetOptional(parameter.Name); - } - - foreach (var inlineConstraint in parameter.InlineConstraints) - { - constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint); - } - } - - return constraintBuilder.Build(); - } - - private static RouteValueDictionary GetDefaults( - RouteTemplate parsedTemplate, - RouteValueDictionary defaults) - { - var result = defaults == null ? new RouteValueDictionary() : new RouteValueDictionary(defaults); - - foreach (var parameter in parsedTemplate.Parameters) - { - if (parameter.DefaultValue != null) - { - if (result.TryGetValue(parameter.Name, out var value)) + var createdPolicy = parameterPolicyFactory.Create(parameter, parameterPolicy); + if (createdPolicy is IRouteConstraint routeConstraint) { - if (!object.Equals(value, parameter.DefaultValue)) + if (!constraints.TryGetValue(parameter.Name, out var paramConstraints)) { - throw new InvalidOperationException( - string.Format(CultureInfo.CurrentCulture, "The route parameter '{0}' has both an inline default value and an explicit default value specified. A route parameter cannot contain an inline default value when a default value is specified explicitly. Consider removing one of them.", parameter.Name)); + paramConstraints = new List(); + constraints.Add(parameter.Name, paramConstraints); } - } - else - { - result.Add(parameter.Name, parameter.DefaultValue); + + paramConstraints.Add(routeConstraint); } } } - return result; + return constraints; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 2f40d07011..4150ed26f3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -12,7 +13,6 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; -using Microsoft.AspNetCore.Routing.Template; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Internal @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private readonly object _lock = new object(); private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; - private readonly IServiceProvider _serviceProvider; + private readonly DefaultHttpContext _httpContextInstance; private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; private List _endpoints; @@ -55,8 +55,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal _actions = actions; _invokerFactory = invokerFactory; - _serviceProvider = serviceProvider; _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); + _httpContextInstance = new DefaultHttpContext() { RequestServices = serviceProvider }; ConventionalEndpointInfos = new List(); @@ -67,7 +67,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal private List CreateEndpoints() { - List endpoints = new List(); + var endpoints = new List(); + StringBuilder patternStringBuilder = null; foreach (var action in _actions.ActionDescriptors.Items) { @@ -81,8 +82,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. var conventionalRouteOrder = 0; - // Check each of the conventional templates to see if the action would be reachable - // If the action and template are compatible then create an endpoint with the + // Check each of the conventional patterns to see if the action would be reachable + // If the action and pattern are compatible then create an endpoint with the // area/controller/action parameter parts replaced with literals // // e.g. {controller}/{action} with HomeController.Index and HomeController.Login @@ -91,32 +92,30 @@ namespace Microsoft.AspNetCore.Mvc.Internal // - Home/Login foreach (var endpointInfo in ConventionalEndpointInfos) { - var actionRouteValues = action.RouteValues; - var endpointTemplateSegments = endpointInfo.ParsedTemplate.Segments; - if (MatchRouteValue(action, endpointInfo, "Area") && MatchRouteValue(action, endpointInfo, "Controller") && MatchRouteValue(action, endpointInfo, "Action")) { - var newEndpointTemplate = TemplateParser.Parse(endpointInfo.Template); + var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); - for (var i = 0; i < newEndpointTemplate.Segments.Count; i++) + for (var i = 0; i < newPathSegments.Count; i++) { - // Check if the template can be shortened because the remaining parameters are optional + // Check if the pattern can be shortened because the remaining parameters are optional // - // e.g. Matching template {controller=Home}/{action=Index}/{id?} against HomeController.Index + // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index // can resolve to the following endpoints: // - /Home/Index/{id?} // - /Home // - / - if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newEndpointTemplate)) + if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) { - var subTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments.Take(i)); + var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, endpointInfo.Name, - subTemplate, + GetPattern(ref patternStringBuilder, subPathSegments), + subPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, @@ -124,25 +123,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpoints.Add(subEndpoint); } - var segment = newEndpointTemplate.Segments[i]; + List segmentParts = null; // Initialize only as needed + var segment = newPathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; - if (part.IsParameter && IsMvcParameter(part.Name)) + if (part.IsParameter && part is RoutePatternParameterPart parameterPart && IsMvcParameter(parameterPart.Name)) { + if (segmentParts == null) + { + segmentParts = segment.Parts.ToList(); + } + // Replace parameter with literal value - segment.Parts[j] = TemplatePart.CreateLiteral(action.RouteValues[part.Name]); + segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); } } - } - var newTemplate = RouteTemplateWriter.ToString(newEndpointTemplate.Segments); + // A parameter part was replaced so replace segment with updated parts + if (segmentParts != null) + { + newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + } + } var endpoint = CreateEndpoint( action, endpointInfo.Name, - newTemplate, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, @@ -157,6 +167,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal action, action.AttributeRouteInfo.Name, action.AttributeRouteInfo.Template, + RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo, @@ -166,6 +177,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal } return endpoints; + + string GetPattern(ref StringBuilder sb, IEnumerable segments) + { + if (sb == null) + { + sb = new StringBuilder(); + } + + RoutePatternWriter.WriteString(sb, segments); + var rawPattern = sb.ToString(); + sb.Length = 0; + + return rawPattern; + } } private bool IsMvcParameter(string name) @@ -184,28 +209,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, - RouteTemplate template) + List pathSegments) { // Check whether the remaining segments are all optional and one or more of them is // for area/controller/action and has a default value var usedDefaultValue = false; - for (var i = segmentIndex; i < template.Segments.Count; i++) + for (var i = segmentIndex; i < pathSegments.Count; i++) { - var segment = template.Segments[i]; + var segment = pathSegments[i]; for (var j = 0; j < segment.Parts.Count; j++) { var part = segment.Parts[j]; - if (part.IsOptional || part.IsOptionalSeperator || part.IsCatchAll) + if (part.IsParameter && part is RoutePatternParameterPart parameterPart) { - continue; - } - if (part.IsParameter) - { - if (IsMvcParameter(part.Name)) + if (parameterPart.IsOptional || parameterPart.IsCatchAll) { - if (endpointInfo.MergedDefaults[part.Name] is string defaultValue - && action.RouteValues.TryGetValue(part.Name, out var routeValue) + continue; + } + + if (IsMvcParameter(parameterPart.Name)) + { + if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue + && action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) { usedDefaultValue = true; @@ -213,6 +239,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } } + else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart + && separatorPart.Content == ".") + { + // Check if this pattern ends in an optional extension, e.g. ".{ext?}" + // Current literal must be "." and followed by a single optional parameter part + var nextPartIndex = j + 1; + + if (nextPartIndex == segment.Parts.Count - 1 + && segment.Parts[nextPartIndex].IsParameter + && segment.Parts[nextPartIndex] is RoutePatternParameterPart extensionParameterPart + && extensionParameterPart.IsOptional) + { + continue; + } + } // Stop because there is a non-optional/non-defaulted trailing value return false; @@ -227,8 +268,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (!action.RouteValues.TryGetValue(routeKey, out var actionValue) || string.IsNullOrWhiteSpace(actionValue)) { // Action does not have a value for this routeKey, most likely because action is not in an area - // Check that the template does not have a parameter for the routeKey - var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); + // Check that the pattern does not have a parameter for the routeKey + var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); if (matchingParameter == null) { return true; @@ -241,18 +282,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal return true; } - var matchingParameter = endpointInfo.ParsedTemplate.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); + var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); if (matchingParameter != null) { // Check that the value matches against constraints on that parameter // e.g. For {controller:regex((Home|Login))} the controller value must match the regex - // - // REVIEW: This is really ugly - if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraint) - && !constraint.Match(new DefaultHttpContext() { RequestServices = _serviceProvider }, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraints)) { - // Did not match constraint - return false; + foreach (var constraint in constraints) + { + if (!constraint.Match(_httpContextInstance, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + { + // Did not match constraint + return false; + } + } } return true; @@ -265,7 +309,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal private RouteEndpoint CreateEndpoint( ActionDescriptor action, string routeName, - string template, + string patternRawText, + IEnumerable segments, object nonInlineDefaults, int order, object source, @@ -301,7 +346,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoint = new RouteEndpoint( requestDelegate, - RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), + RoutePatternFactory.Pattern(patternRawText, defaults, parameterPolicies: null, segments), order, metadataCollection, action.DisplayName); @@ -377,13 +422,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Template: {controller}/{action}/{category}/{id?} // Defaults(in-line or non in-line): category=products // Required values: controller=foo, action=bar - // Final constructed template: foo/bar/{category}/{id?} + // Final constructed pattern: foo/bar/{category}/{id?} // Final defaults: controller=foo, action=bar, category=products // // Template: {controller=Home}/{action=Index}/{category=products}/{id?} // Defaults: controller=Home, action=Index, category=products // Required values: controller=foo, action=bar - // Final constructed template: foo/bar/{category}/{id?} + // Final constructed pattern: foo/bar/{category}/{id?} // Final defaults: controller=foo, action=bar, category=products private void EnsureRequiredValuesInDefaults(IDictionary requiredValues, RouteValueDictionary defaults) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs new file mode 100644 index 0000000000..5beeb6baa6 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs @@ -0,0 +1,75 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Microsoft.AspNetCore.Routing.Patterns; + +namespace Microsoft.AspNetCore.Mvc.Internal +{ + internal static class RoutePatternWriter + { + public static void WriteString(StringBuilder sb, IEnumerable routeSegments) + { + foreach (var segment in routeSegments) + { + if (sb.Length > 0) + { + sb.Append("/"); + } + + WriteString(sb, segment); + } + } + + private static void WriteString(StringBuilder sb, RoutePatternPathSegment segment) + { + for (int i = 0; i < segment.Parts.Count; i++) + { + WriteString(sb, segment.Parts[i]); + } + } + + private static void WriteString(StringBuilder sb, RoutePatternPart part) + { + if (part.IsParameter && part is RoutePatternParameterPart parameterPart) + { + sb.Append("{"); + if (parameterPart.IsCatchAll) + { + sb.Append("*"); + } + sb.Append(parameterPart.Name); + foreach (var item in parameterPart.ParameterPolicies) + { + sb.Append(":"); + sb.Append(item.Content); + } + if (parameterPart.Default != null) + { + sb.Append("="); + sb.Append(parameterPart.Default); + } + if (parameterPart.IsOptional) + { + sb.Append("?"); + } + sb.Append("}"); + } + else if (part is RoutePatternLiteralPart literalPart) + { + sb.Append(literalPart.Content); + } + else if (part is RoutePatternSeparatorPart separatorPart) + { + sb.Append(separatorPart.Content); + } + else + { + throw new NotSupportedException(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs deleted file mode 100644 index b17df21be2..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RouteTemplateWriter.cs +++ /dev/null @@ -1,56 +0,0 @@ -// 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 Microsoft.AspNetCore.Routing.Template; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - internal static class RouteTemplateWriter - { - public static string ToString(IEnumerable routeSegments) - { - return string.Join("/", routeSegments.Select(s => ToString(s))); - } - - private static string ToString(TemplateSegment templateSegment) - { - return string.Join(string.Empty, templateSegment.Parts.Select(p => ToString(p))); - } - - private static string ToString(TemplatePart templatePart) - { - if (templatePart.IsParameter) - { - var partText = "{"; - if (templatePart.IsCatchAll) - { - partText += "*"; - } - partText += templatePart.Name; - foreach (var item in templatePart.InlineConstraints) - { - partText += ":"; - partText += item.Constraint; - } - if (templatePart.DefaultValue != null) - { - partText += "="; - partText += templatePart.DefaultValue; - } - if (templatePart.IsOptional) - { - partText += "?"; - } - partText += "}"; - - return partText; - } - else - { - return templatePart.Text; - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index 46d9adb91c..f858f4fe1e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Run really late. public override int Order => 100000; - public void Apply(HttpContext httpContext, CandidateSet candidateSet) + public Task ApplyAsync(HttpContext httpContext, EndpointFeature endpointFeature, CandidateSet candidateSet) { // PERF: we can skip over action constraints if there aren't any app-wide. // @@ -47,6 +47,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing { ApplyActionConstraints(httpContext, candidateSet); } + + return Task.CompletedTask; } private void ApplyActionConstraints( diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs index d731a697f2..3bd2cfa1f6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Builder var endpointInfo = Assert.Single(mvcEndpointDataSource.ConventionalEndpointInfos); Assert.Equal("default", endpointInfo.Name); - Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Template); + Assert.Equal("{controller=Home}/{action=Index}/{id?}", endpointInfo.Pattern); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index ca09424a52..55fd51e0b8 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -15,6 +15,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Microsoft.Extensions.Primitives; using Moq; @@ -185,9 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal [InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })] [InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })] [InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })] - //[InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })] - //[InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })] - public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + [InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })] + [InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })] + public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -199,7 +200,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; // Assert - var inspectors = finalEndpointTemplates + var inspectors = finalEndpointPatterns .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); @@ -694,12 +695,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal IDictionary constraints = null, RouteValueDictionary dataTokens = null) { - var routeOptions = new RouteOptions(); - var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); - routeOptionsSetup.Configure(routeOptions); + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRouting(); - var constraintResolver = new DefaultInlineConstraintResolver(Options.Create(routeOptions)); - return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, constraintResolver); + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + serviceCollection.Configure(routeOptionsSetup.Configure); + + var serviceProvider = serviceCollection.BuildServiceProvider(); + + var parameterPolicyFactory = serviceProvider.GetRequiredService(); + return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, parameterPolicyFactory); } private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs similarity index 73% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs index 68fcb544ac..c403213b72 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RouteTemplateWriterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs @@ -1,13 +1,14 @@ // 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.Text; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Routing.Template; +using Microsoft.AspNetCore.Routing.Patterns; using Xunit; namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal { - public class RouteTemplateWriterTests + public class RoutePatternWriterTests { [Theory] [InlineData(@"")] @@ -23,11 +24,12 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Internal [InlineData(@"{p1}.{p2}.{p3}")] public void ToString_TemplateRoundtrips(string template) { - var routeTemplate = TemplateParser.Parse(template); + var routePattern = RoutePatternFactory.Parse(template); - var output = RouteTemplateWriter.ToString(routeTemplate.Segments); + var sb = new StringBuilder(); + RoutePatternWriter.WriteString(sb, routePattern.PathSegments); - Assert.Equal(template, output); + Assert.Equal(template, sb.ToString()); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index a423dfd2e3..e2f02e7f63 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public class ActionConstraintMatcherPolicyTest { [Fact] - public void Apply_CanBeAmbiguous() + public async Task Apply_CanBeAmbiguous() { // Arrange var actions = new ActionDescriptor[] @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var selector = CreateSelector(actions); // Act - selector.Apply(new DefaultHttpContext(), candidateSet); + await selector.ApplyAsync(new DefaultHttpContext(), new EndpointFeature(), candidateSet); // Assert Assert.True(candidateSet[0].IsValidCandidate); @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_PrefersActionWithConstraints() + public async Task Apply_PrefersActionWithConstraints() { // Arrange var actionWithConstraints = new ActionDescriptor() @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.True(candidateSet[0].IsValidCandidate); @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_ConstraintsRejectAll() + public async Task Apply_ConstraintsRejectAll() { // Arrange var action1 = new ActionDescriptor() @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.False(candidateSet[0].IsValidCandidate); @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_ConstraintsRejectAll_DifferentStages() + public async Task Apply_ConstraintsRejectAll_DifferentStages() { // Arrange var action1 = new ActionDescriptor() @@ -138,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.False(candidateSet[0].IsValidCandidate); @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Due to ordering of stages, the first action will be better. [Fact] - public void Apply_ConstraintsInOrder() + public async Task Apply_ConstraintsInOrder() { // Arrange var best = new ActionDescriptor() @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.True(candidateSet[0].IsValidCandidate); @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_SkipsOverInvalidEndpoints() + public async Task Apply_SkipsOverInvalidEndpoints() { // Arrange var best = new ActionDescriptor() @@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.False(candidateSet[0].IsValidCandidate); @@ -220,7 +220,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_IncludesNonMvcEndpoints() + public async Task Apply_IncludesNonMvcEndpoints() { // Arrange var action1 = new ActionDescriptor() @@ -246,7 +246,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.False(candidateSet[0].IsValidCandidate); @@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Due to ordering of stages, the first action will be better. [Fact] - public void Apply_ConstraintsInOrder_MultipleStages() + public async Task Apply_ConstraintsInOrder_MultipleStages() { // Arrange var best = new ActionDescriptor() @@ -287,7 +287,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.True(candidateSet[0].IsValidCandidate); @@ -295,7 +295,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void Apply_Fallback_ToActionWithoutConstraints() + public async Task Apply_Fallback_ToActionWithoutConstraints() { // Arrange var nomatch1 = new ActionDescriptor() @@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - selector.Apply(httpContext, candidateSet); + await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); // Assert Assert.True(candidateSet[0].IsValidCandidate); From 8eea0ad44c4d469abc2c2d52f1c79505bd6afaeb Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 14 Aug 2018 16:13:17 -0700 Subject: [PATCH 188/316] Update tests to latest compat switch --- samples/MvcSandbox/Startup.cs | 2 +- .../Internal/MvcEndpointDataSource.cs | 15 +--- .../Internal/MvcEndpointDataSourceTests.cs | 4 +- .../ApiExplorerTest.cs | 2 +- .../ApplicationModelTest.cs | 2 +- .../CorsEndpointRoutingTests.cs | 25 +------ .../CorsTests.cs | 25 ++++++- .../CorsTestsBase.cs | 2 +- .../EndpointRoutingTest.cs | 39 +--------- .../GlobalAuthorizationFilterTest.cs | 10 +-- .../HtmlGenerationTest.cs | 40 +++++++++- .../Infrastructure/MvcTestFixture.cs | 4 - .../RazorPagesTest.cs | 2 +- .../RoutingTests.cs | 61 +++++++++++++++- .../RoutingTestsBase.cs | 49 ++----------- .../VersioningEndpointRoutingTests.cs | 4 +- .../VersioningTests.cs | 4 +- ...ite.HtmlGeneration_Home.Index.Encoded.html | 14 ++-- ...tionWebSite.HtmlGeneration_Home.Index.html | 14 ++-- ...ite.HtmlGeneration_Home.Index21Compat.html | 73 +++++++++++++++++++ .../TagHelpersWebSite.Home.About.html | 4 +- .../TagHelpersWebSite.Home.Help.html | 4 +- .../TagHelpersWebSite.Home.Index.html | 4 +- ...sWebSite.Home.ViewComponentTagHelpers.html | 4 +- .../ActionDescriptorChangeProvider.cs | 18 +++-- .../ApiExplorerRouteChangeConvention.cs | 35 +++++++++ .../ApiExplorerReloadableController.cs | 28 +------ .../ApiExplorerWebSite/ReloadAttribute.cs | 8 ++ test/WebSites/ApiExplorerWebSite/Startup.cs | 11 ++- .../WellKnownChangeToken.cs | 12 +++ .../ApplicationModelWebSite/Startup.cs | 4 +- .../BasicWebSite/StartupRequestLimitSize.cs | 4 +- ...hCookieTempDataProviderAndCookieConsent.cs | 4 +- .../StartupWithSessionTempDataProvider.cs | 4 +- .../ControllersFromServicesWebSite/Startup.cs | 5 +- test/WebSites/CorsWebSite/Startup.cs | 6 +- ...pointRouting.cs => StartupWith21Compat.cs} | 8 +- .../ErrorPageMiddlewareWebSite/Startup.cs | 4 +- test/WebSites/FilesWebSite/Startup.cs | 4 +- .../WebSites/HtmlGenerationWebSite/Startup.cs | 5 +- .../StartupWith21CompatibilityBehavior.cs | 55 ++++++++++++++ .../StartupWithCultureReplace.cs | 1 - .../Views/HtmlGeneration_Home/Index.cshtml | 4 +- test/WebSites/RazorBuildWebSite/Startup.cs | 4 +- test/WebSites/RazorPagesWebSite/Startup.cs | 4 +- .../RazorPagesWebSite/StartupWithBasePath.cs | 4 +- .../Controllers/EmbeddedViewsController.cs | 2 + test/WebSites/RazorWebSite/Startup.cs | 4 +- .../RazorWebSite/StartupDataAnnotations.cs | 5 +- test/WebSites/RoutingWebSite/Startup.cs | 12 ++- ...pointRouting.cs => StartupWith21Compat.cs} | 5 +- test/WebSites/SecurityWebSite/Startup.cs | 4 +- ...ith20CompatAndGlobalDenyAnonymousFilter.cs | 41 +++++++++++ .../StartupWithGlobalDenyAnonymousFilter.cs | 4 +- test/WebSites/SimpleWebSite/Startup.cs | 6 +- test/WebSites/TagHelpersWebSite/Startup.cs | 4 +- test/WebSites/VersioningWebSite/Startup.cs | 6 +- ...pointRouting.cs => StartupWith21Compat.cs} | 7 +- .../WebApiCompatShimWebSite/Startup.cs | 6 +- test/WebSites/XmlFormattersWebSite/Startup.cs | 3 +- 60 files changed, 495 insertions(+), 253 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html create mode 100644 test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs create mode 100644 test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs create mode 100644 test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs rename test/WebSites/CorsWebSite/{StartupWithEndpointRouting.cs => StartupWith21Compat.cs} (93%) create mode 100644 test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs rename test/WebSites/RoutingWebSite/{StartupWithEndpointRouting.cs => StartupWith21Compat.cs} (90%) create mode 100644 test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs rename test/WebSites/VersioningWebSite/{StartupWithEndpointRouting.cs => StartupWith21Compat.cs} (81%) diff --git a/samples/MvcSandbox/Startup.cs b/samples/MvcSandbox/Startup.cs index 0b3360ec8a..d9f96bf08b 100644 --- a/samples/MvcSandbox/Startup.cs +++ b/samples/MvcSandbox/Startup.cs @@ -14,7 +14,7 @@ namespace MvcSandbox // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Version_2_2); + services.AddMvc().SetCompatibilityVersion(Microsoft.AspNetCore.Mvc.CompatibilityVersion.Latest); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 4150ed26f3..128e699774 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Internal @@ -59,10 +60,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal _httpContextInstance = new DefaultHttpContext() { RequestServices = serviceProvider }; ConventionalEndpointInfos = new List(); - - Extensions.Primitives.ChangeToken.OnChange( - GetCompositeChangeToken, - UpdateEndpoints); } private List CreateEndpoints() @@ -454,7 +451,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return new CompositeChangeToken(changeTokens); } - public override IChangeToken GetChangeToken() => GetCompositeChangeToken(); + public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; public override IReadOnlyList Endpoints { @@ -479,14 +476,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private void UpdateEndpoints() - { - lock (_lock) - { - _endpoints = CreateEndpoints(); - } - } - public List ConventionalEndpointInfos { get; } private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 55fd51e0b8..fbee5e6f55 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.True(actionInvokerCalled); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] public void GetChangeToken_MultipleChangeTokenProviders_ComposedResult() { // Arrange @@ -287,7 +287,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] public void Endpoints_ChangeTokenTriggered_EndpointsRecreated() { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index d216ad4aff..40e556ad72 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1087,7 +1087,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] public async Task ApiExplorer_Updates_WhenActionDescriptorCollectionIsUpdated() { // Act - 1 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs index a698af7ba4..dcb50e1d8e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("From Header - HelloWorld", body); } - [Fact] + [Fact(Skip = "https://github.com/aspnet/Routing/issues/721")] public async Task ActionModelSuppressedForPathMatching_CannotBeRouted() { // Arrange & Act diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs index 44402ff200..82867e6ed5 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs @@ -1,34 +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.Net; -using System.Net.Http; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Cors.Infrastructure; -using Xunit; - namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class CorsEndpointRoutingTests : CorsTestsBase + public class CorsEndpointRoutingTests : CorsTestsBase { - public CorsEndpointRoutingTests(MvcTestFixture fixture) + public CorsEndpointRoutingTests(MvcTestFixture fixture) : base(fixture) { } - - [Fact] // This intentionally returns a 405 with endpoint routing - public override async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() - { - // Arrange - var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/Post"); - request.Headers.Add(CorsConstants.Origin, "http://example.com"); - request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); - - // Act - var response = await Client.SendAsync(request); - - // Assert - Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs index 4d241ddeb0..512fc5ea90 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs @@ -1,13 +1,34 @@ // 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cors.Infrastructure; +using Xunit; + namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class CorsTests : CorsTestsBase + public class CorsTests : CorsTestsBase { - public CorsTests(MvcTestFixture fixture) + public CorsTests(MvcTestFixture fixture) : base(fixture) { } + + [Fact] + public override async Task PreflightRequestOnNonCorsEnabledController_DoesNotMatchTheAction() + { + // Arrange + var request = new HttpRequestMessage(new HttpMethod("OPTIONS"), "http://localhost/NonCors/Post"); + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "POST"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs index 899ce4e792..01e9e900eb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs @@ -94,7 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var response = await Client.SendAsync(request); // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } [Theory] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index 81a21798eb..f82be33703 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -10,9 +10,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class EndpointRoutingTest : RoutingTestsBase + public class EndpointRoutingTest : RoutingTestsBase { - public EndpointRoutingTest(MvcTestFixture fixture) + public EndpointRoutingTest(MvcTestFixture fixture) : base(fixture) { } @@ -32,26 +32,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.True(result); } - [Fact] - public override Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() - { - // By design, this test cannot work in EndpointRouting world. This is because in case of old routing test - // when a link generation to an attribute routed controller with a non-existing action does not succeeed, - // the next route in the route collection is considered and since the next route in the route collection is - // a conventional area route, the old routing test succeeds. But this cannot happen in case of endpoint - // routing as the action does not exist to begin with. - return Task.CompletedTask; - } - - [Fact] - public override Task ConventionalRoutedAction_InArea_StaysInArea() - { - // By design, this test cannot work in EndpointRouting world. In old routing test a link is being generated - // to a non-existing action on a controller which is in an area. In case of endpoint routing, we cannot - // generate links as the action does not exist to begin with. - return Task.CompletedTask; - } - [Fact] public async override Task RouteData_Routers_ConventionalRoute() { @@ -97,21 +77,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } - // Endpoint routing exposes HTTP 405s for HTTP method mismatches - [Fact] - public override async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() - { - // Arrange - var url = "http://localhost/api/v1/Maps"; - - // Act - var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); - - // Assert - Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); - } - - // Endpoint routing exposes HTTP 405s for HTTP method mismatches [Theory] [MemberData(nameof(AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraintsData))] public override async Task AttributeRoutedAction_MultipleRouteAttributes_WithMultipleHttpAttributes_RespectsConstraints( diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs index 23dce2a053..5ac55864af 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs @@ -6,9 +6,7 @@ using System.Net; using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; -using Microsoft.Extensions.DependencyInjection; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -58,11 +56,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task AuthorizationPoliciesDoNotCombine_WithV2_0() { // Arrange & Act - var factory = Factory.WithWebHostBuilder( - builder => builder.ConfigureServices( - services => services.Configure( - options => options.CompatibilityVersion = CompatibilityVersion.Version_2_0))); - var client = factory.CreateDefaultClient(); + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); var response = await client.PostAsync("http://localhost/Administration/SignInCookie2", null); // Assert diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index 8c7984814e..65ba3292dd 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -10,8 +10,8 @@ using System.Net.Http.Headers; using System.Reflection; using System.Text; using System.Threading.Tasks; -using AngleSharp.Dom; -using AngleSharp.Dom.Html; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Testing; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -26,14 +26,21 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests MvcTestFixture fixture, MvcEncodedTestFixture encodedFixture) { + Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); + Client = fixture.CreateDefaultClient(); EncodedClient = encodedFixture.CreateDefaultClient(); } + private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => + builder.UseStartup(); + public HttpClient Client { get; } public HttpClient EncodedClient { get; } + public WebApplicationFactory Factory { get; } + public static TheoryData WebPagesData { get @@ -131,6 +138,35 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } } + [Fact] + public async Task HtmlGenerationWebSite_LinkGeneration_With21CompatibilityBehavior() + { + // Arrange + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); + var expectedMediaType = MediaTypeHeaderValue.Parse("text/html; charset=utf-8"); + var outputFile = "compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html"; + var expectedContent = + await ResourceFile.ReadResourceAsync(_resourcesAssembly, outputFile, sourceFile: false); + + // Act + // The host is not important as everything runs in memory and tests are isolated from each other. + var response = await client.GetAsync("http://localhost/HtmlGeneration_Home/"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal(expectedMediaType, response.Content.Headers.ContentType); + + responseContent = responseContent.Trim(); +#if GENERATE_BASELINES + ResourceFile.UpdateFile(_resourcesAssembly, outputFile, expectedContent, responseContent); +#else + Assert.Equal(expectedContent.Trim(), responseContent, ignoreLineEndingDifferences: true); +#endif + } + public static TheoryData EncodedPagesData { get diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs index a7d4a643de..cf1ed53098 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs @@ -3,7 +3,6 @@ using System.Globalization; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; @@ -26,9 +25,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var testSink = new TestSink(); var loggerFactory = new TestLoggerFactory(testSink, enabled: true); services.AddSingleton(loggerFactory); - - services.Configure( - options => options.CompatibilityVersion = CompatibilityVersion.Version_2_1); }); } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 88be27634f..3c90af4882 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -919,7 +919,7 @@ Hello from /Pages/WithViewStart/Index.cshtml!"; { // Arrange var expected = -@"Microsoft.AspNetCore.Mvc.Routing.UrlHelper +@"Microsoft.AspNetCore.Mvc.Routing.EndpointRoutingUrlHelper Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper`1[AspNetCore.InjectedPageProperties] Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPageProperties]"; diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs index a4fbb4ab58..538d06358c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Net; +using System.Net.Http; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; @@ -10,9 +11,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class RoutingTests : RoutingTestsBase + public class RoutingTests : RoutingTestsBase { - public RoutingTests(MvcTestFixture fixture) + public RoutingTests(MvcTestFixture fixture) : base(fixture) { } @@ -32,6 +33,62 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.False(result); } + // Legacy routing supports linking to actions that don't exist + [Fact] + public async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() + { + // Arrange + var url = LinkFrom("http://localhost/ContosoCorp/Trains") + .To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Rail", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_InArea_StaysInArea() + { + // Arrange + var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("Flight", result.Controller); + Assert.Equal("Index", result.Action); + + Assert.Equal("/Travel/Home/Contact", result.Link); + } + + // Legacy routing returns 404 when an action does not support a HTTP method. + [Fact] + public override async Task AttributeRoutedAction_MultipleRouteAttributes_RouteAttributeTemplatesIgnoredForOverrideActions() + { + // Arrange + var url = "http://localhost/api/v1/Maps"; + + // Act + var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + [Fact] public async override Task RouteData_Routers_ConventionalRoute() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 4767f77696..6430ff2005 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var response = await Client.SendAsync(new HttpRequestMessage(new HttpMethod("POST"), url)); // Assert - Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } [Theory] @@ -1016,26 +1016,6 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("/", result.Link); } - [Fact] - public virtual async Task ConventionalRoutedAction_InArea_StaysInArea() - { - // Arrange - var url = LinkFrom("http://localhost/Travel/Flight").To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Flight", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } - [Fact] public virtual async Task AttributeRoutedAction_LinkToArea() { @@ -1098,26 +1078,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("/", result.Link); } - [Fact] - public virtual async Task AttributeRoutedAction_InArea_StaysInArea_ActionDoesntExist() - { - // Arrange - var url = LinkFrom("http://localhost/ContosoCorp/Trains") - .To(new { action = "Contact", controller = "Home", }); - - // Act - var response = await Client.GetAsync(url); - - // Assert - Assert.Equal(HttpStatusCode.OK, response.StatusCode); - var body = await response.Content.ReadAsStringAsync(); - var result = JsonConvert.DeserializeObject(body); - - Assert.Equal("Rail", result.Controller); - Assert.Equal("Index", result.Action); - - Assert.Equal("/Travel/Home/Contact", result.Link); - } + [Fact] public virtual async Task AttributeRoutedAction_InArea_LinkToConventionalRoutedActionInArea() @@ -1280,13 +1241,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(actionName, result.Action); } - private static LinkBuilder LinkFrom(string url) + protected static LinkBuilder LinkFrom(string url) { return new LinkBuilder(url); } // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - private class RoutingResult + protected class RoutingResult { public string[] ExpectedUrls { get; set; } @@ -1303,7 +1264,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public string Link { get; set; } } - private class LinkBuilder + protected class LinkBuilder { public LinkBuilder(string url) { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs index 2aeb0d2490..30f2ea33ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs @@ -9,9 +9,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class VersioningEndpointRoutingTests : VersioningTestsBase + public class VersioningEndpointRoutingTests : VersioningTestsBase { - public VersioningEndpointRoutingTests(MvcTestFixture fixture) + public VersioningEndpointRoutingTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs index 40e14bdc57..80e057bbd1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs @@ -8,9 +8,9 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class VersioningTests : VersioningTestsBase + public class VersioningTests : VersioningTestsBase { - public VersioningTests(MvcTestFixture fixture) + public VersioningTests(MvcTestFixture fixture) : base(fixture) { } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html index dfed464b45..c518a8fea6 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html @@ -13,7 +13,7 @@ Default Controller
Product Submit Fragment @@ -39,32 +39,32 @@
Non-existent Area diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html index e80ec4969e..1800bea8a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html @@ -13,7 +13,7 @@ Default Controller
Product Submit Fragment @@ -39,32 +39,32 @@
Non-existent Area diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html new file mode 100644 index 0000000000..59380d72d6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html index e675616a89..72ba50bb99 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html @@ -12,8 +12,8 @@

ASP.NET vNext - About

| My Home - | My About - | My Help |

+ | My About + | My Help |

diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html index 0a20fbb435..a3d85388ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html @@ -12,8 +12,8 @@

ASP.NET vNext - Help

| My Home - | My About - | My Help |

+ | My About + | My Help |

diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html index 27bc4cf95a..d10ba0b6f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html @@ -19,8 +19,8 @@

ASP.NET vNext - Home Page

| My Home - | My About - | My Help |

+ | My About + | My Help |

diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html index b6caedd04c..c9bec6c144 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html @@ -12,8 +12,8 @@

ASP.NET vNext -

| My Home - | My About - | My Help |

+ | My About + | My Help |

Items:
diff --git a/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs b/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs index abc1b3b8b3..266f99d3aa 100644 --- a/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs +++ b/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs @@ -9,20 +9,22 @@ namespace ApiExplorerWebSite { public class ActionDescriptorChangeProvider : IActionDescriptorChangeProvider { - private ActionDescriptorChangeProvider() + public ActionDescriptorChangeProvider(WellKnownChangeToken changeToken) { + ChangeToken = changeToken; } - public static ActionDescriptorChangeProvider Instance { get; } = new ActionDescriptorChangeProvider(); - - public CancellationTokenSource TokenSource { get; private set; } - - public bool HasChanged { get; set; } + public WellKnownChangeToken ChangeToken { get; } public IChangeToken GetChangeToken() { - TokenSource = new CancellationTokenSource(); - return new CancellationChangeToken(TokenSource.Token); + if (ChangeToken.TokenSource.IsCancellationRequested) + { + var changeTokenSource = new CancellationTokenSource(); + return new CancellationChangeToken(changeTokenSource.Token); + } + + return new CancellationChangeToken(ChangeToken.TokenSource.Token); } } } diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs b/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs new file mode 100644 index 0000000000..c329b28c42 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; + +namespace ApiExplorerWebSite +{ + public class ApiExplorerRouteChangeConvention : Attribute, IActionModelConvention + { + public ApiExplorerRouteChangeConvention(WellKnownChangeToken changeToken) + { + ChangeToken = changeToken; + } + + public WellKnownChangeToken ChangeToken { get; } + + public void Apply(ActionModel action) + { + if (action.Attributes.OfType().Any() && ChangeToken.TokenSource.IsCancellationRequested) + { + action.ActionName = "NewIndex"; + action.Selectors.Clear(); + action.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = "NewIndex" + } + }); + } + } + } +} diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs index 31103ba0f2..d6372a8a86 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs @@ -1,45 +1,23 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.ApplicationModels; namespace ApiExplorerWebSite { [Route("ApiExplorerReload")] public class ApiExplorerReloadableController : Controller { - [ApiExplorerRouteChangeConvention] [Route("Index")] + [Reload] public string Index() => "Hello world"; [Route("Reload")] [PassThru] - public IActionResult Reload() + public IActionResult Reload([FromServices] WellKnownChangeToken changeToken) { - ActionDescriptorChangeProvider.Instance.HasChanged = true; - ActionDescriptorChangeProvider.Instance.TokenSource.Cancel(); + changeToken.TokenSource.Cancel(); return Ok(); } - - public class ApiExplorerRouteChangeConventionAttribute : Attribute, IActionModelConvention - { - public void Apply(ActionModel action) - { - if (ActionDescriptorChangeProvider.Instance.HasChanged) - { - action.ActionName = "NewIndex"; - action.Selectors.Clear(); - action.Selectors.Add(new SelectorModel - { - AttributeRouteModel = new AttributeRouteModel - { - Template = "NewIndex" - } - }); - } - } - } } } diff --git a/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs b/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs new file mode 100644 index 0000000000..9cb7222539 --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs @@ -0,0 +1,8 @@ +using System; + +namespace ApiExplorerWebSite +{ + public class ReloadAttribute : Attribute + { + } +} diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/test/WebSites/ApiExplorerWebSite/Startup.cs index e794f807ef..f5f96abfd5 100644 --- a/test/WebSites/ApiExplorerWebSite/Startup.cs +++ b/test/WebSites/ApiExplorerWebSite/Startup.cs @@ -6,6 +6,7 @@ using System.Linq; using ApiExplorerWebSite.Controllers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -19,6 +20,8 @@ namespace ApiExplorerWebSite public void ConfigureServices(IServiceCollection services) { services.AddTransient(); + + var wellKnownChangeToken = new WellKnownChangeToken(); services.AddMvc(options => { options.Filters.AddService(typeof(ApiExplorerDataFilter)); @@ -28,17 +31,19 @@ namespace ApiExplorerWebSite typeof(ApiExplorerVisbilityDisabledByConventionController))); options.Conventions.Add(new ApiExplorerInboundOutboundConvention( typeof(ApiExplorerInboundOutBoundController))); + options.Conventions.Add(new ApiExplorerRouteChangeConvention(wellKnownChangeToken)); var jsonOutputFormatter = options.OutputFormatters.OfType().First(); options.OutputFormatters.Clear(); options.OutputFormatters.Add(jsonOutputFormatter); options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter()); - }); + }) + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddSingleton(); - services.AddSingleton(ActionDescriptorChangeProvider.Instance); - services.AddSingleton(ActionDescriptorChangeProvider.Instance); + services.AddSingleton(); + services.AddSingleton(wellKnownChangeToken); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs b/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs new file mode 100644 index 0000000000..a862f4f88f --- /dev/null +++ b/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs @@ -0,0 +1,12 @@ +// 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.Threading; + +namespace ApiExplorerWebSite +{ + public class WellKnownChangeToken + { + public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); + } +} diff --git a/test/WebSites/ApplicationModelWebSite/Startup.cs b/test/WebSites/ApplicationModelWebSite/Startup.cs index 37e97287fb..899b3c13fa 100644 --- a/test/WebSites/ApplicationModelWebSite/Startup.cs +++ b/test/WebSites/ApplicationModelWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace ApplicationModelWebSite @@ -20,7 +21,8 @@ namespace ApplicationModelWebSite options.Conventions.Add(new FromHeaderConvention()); options.Conventions.Add(new MultipleAreasControllerConvention()); options.Conventions.Add(new CloneActionConvention()); - }); + }) + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs b/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs index 0338d0b217..066fc406da 100644 --- a/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs +++ b/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs @@ -5,6 +5,7 @@ using System; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -13,7 +14,8 @@ namespace BasicWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.ConfigureBaseWebSiteAuthPolicies(); } diff --git a/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs b/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs index 0fdb7383b6..db15febae3 100644 --- a/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs +++ b/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -10,7 +11,8 @@ namespace BasicWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.Configure(o => { diff --git a/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs b/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs index c05e83cb95..1d868bc638 100644 --- a/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs +++ b/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -13,7 +14,8 @@ namespace BasicWebSite // CookieTempDataProvider is the default ITempDataProvider, so we must override it with session. services .AddMvc() - .AddSessionStateTempDataProvider(); + .AddSessionStateTempDataProvider() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddSession(); services.ConfigureBaseWebSiteAuthPolicies(); diff --git a/test/WebSites/ControllersFromServicesWebSite/Startup.cs b/test/WebSites/ControllersFromServicesWebSite/Startup.cs index 05653ebde6..b11534fedb 100644 --- a/test/WebSites/ControllersFromServicesWebSite/Startup.cs +++ b/test/WebSites/ControllersFromServicesWebSite/Startup.cs @@ -11,7 +11,7 @@ using ControllersFromServicesWebSite.Components; using ControllersFromServicesWebSite.TagHelpers; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.Extensions.DependencyInjection; @@ -36,7 +36,8 @@ namespace ControllersFromServicesWebSite }) .AddControllersAsServices() .AddViewComponentsAsServices() - .AddTagHelpersAsServices(); + .AddTagHelpersAsServices() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddTransient(); services.AddTransient(); diff --git a/test/WebSites/CorsWebSite/Startup.cs b/test/WebSites/CorsWebSite/Startup.cs index 5c8e115755..6ef084b83b 100644 --- a/test/WebSites/CorsWebSite/Startup.cs +++ b/test/WebSites/CorsWebSite/Startup.cs @@ -1,10 +1,9 @@ // 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.Cors.Infrastructure; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace CorsWebSite @@ -13,7 +12,8 @@ namespace CorsWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.Configure(options => { options.AddPolicy( diff --git a/test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs b/test/WebSites/CorsWebSite/StartupWith21Compat.cs similarity index 93% rename from test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs rename to test/WebSites/CorsWebSite/StartupWith21Compat.cs index c620d62a58..b34b71f80c 100644 --- a/test/WebSites/CorsWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/CorsWebSite/StartupWith21Compat.cs @@ -1,19 +1,19 @@ // 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.Cors.Infrastructure; -using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace CorsWebSite { - public class StartupWithEndpointRouting + public class StartupWith21Compat { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(options => options.EnableEndpointRouting = true); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.Configure(options => { options.AddPolicy( diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs b/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs index 864fce79ea..3fc459a06f 100644 --- a/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs +++ b/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace ErrorPageMiddlewareWebSite @@ -13,7 +14,8 @@ namespace ErrorPageMiddlewareWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/FilesWebSite/Startup.cs b/test/WebSites/FilesWebSite/Startup.cs index aaa363af54..3e1404b3ff 100644 --- a/test/WebSites/FilesWebSite/Startup.cs +++ b/test/WebSites/FilesWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace FilesWebSite @@ -13,7 +14,8 @@ namespace FilesWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/HtmlGenerationWebSite/Startup.cs b/test/WebSites/HtmlGenerationWebSite/Startup.cs index a0c93159d6..a1b2a64b26 100644 --- a/test/WebSites/HtmlGenerationWebSite/Startup.cs +++ b/test/WebSites/HtmlGenerationWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.TagHelpers; using Microsoft.Extensions.DependencyInjection; @@ -16,7 +17,9 @@ namespace HtmlGenerationWebSite { // Add MVC services to the services container. Change default FormTagHelper.AntiForgery to false. Usually // null which is interpreted as true unless element includes an action attribute. - services.AddMvc().InitializeTagHelper((helper, _) => helper.Antiforgery = false); + services.AddMvc() + .InitializeTagHelper((helper, _) => helper.Antiforgery = false) + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddSingleton(typeof(ISignalTokenProviderService<>), typeof(SignalTokenProviderService<>)); services.AddSingleton(); diff --git a/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs b/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs new file mode 100644 index 0000000000..5c4fce1ed8 --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs @@ -0,0 +1,55 @@ +// 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; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.Extensions.DependencyInjection; + +namespace HtmlGenerationWebSite +{ + public class StartupWith21CompatibilityBehavior + { + // Set up application services + public void ConfigureServices(IServiceCollection services) + { + // Add MVC services to the services container. Change default FormTagHelper.AntiForgery to false. Usually + // null which is interpreted as true unless element includes an action attribute. + services.AddMvc() + .InitializeTagHelper((helper, _) => helper.Antiforgery = false) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + + services.AddSingleton(typeof(ISignalTokenProviderService<>), typeof(SignalTokenProviderService<>)); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseStaticFiles(); + app.UseMvc(routes => + { + routes.MapRoute( + name: "areaRoute", + template: "{area:exists}/{controller}/{action}/{id?}", + defaults: new { action = "Index" }); + routes.MapRoute( + name: "productRoute", + template: "Product/{action}", + defaults: new { controller = "Product" }); + routes.MapRoute( + name: "default", + template: "{controller}/{action}/{id?}", + defaults: new { controller = "HtmlGeneration_Home", action = "Index" }); + }); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + new WebHostBuilder() + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup() + .UseKestrel() + .UseIISIntegration(); + } +} diff --git a/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs b/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs index ea197babb3..1214b96f88 100644 --- a/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs +++ b/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs @@ -5,7 +5,6 @@ using System.Globalization; using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.Localization; using Microsoft.Extensions.DependencyInjection; namespace HtmlGenerationWebSite diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml index c505df763e..24830788f5 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml @@ -19,7 +19,7 @@ Default Controller
Product Submit Fragment @@ -46,7 +46,7 @@
diff --git a/test/WebSites/RazorBuildWebSite/Startup.cs b/test/WebSites/RazorBuildWebSite/Startup.cs index b66bede92b..3d4858bd55 100644 --- a/test/WebSites/RazorBuildWebSite/Startup.cs +++ b/test/WebSites/RazorBuildWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace RazorBuildWebSite @@ -12,7 +13,8 @@ namespace RazorBuildWebSite { public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/test/WebSites/RazorPagesWebSite/Startup.cs index fe3cecc4e9..ea3a6bd601 100644 --- a/test/WebSites/RazorPagesWebSite/Startup.cs +++ b/test/WebSites/RazorPagesWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.Globalization; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using RazorPagesWebSite.Conventions; @@ -25,7 +26,8 @@ namespace RazorPagesWebSite options.Conventions.AddPageRoute("/Pages/NotTheRoot", string.Empty); options.Conventions.Add(new CustomModelTypeConvention()); }) - .WithRazorPagesAtContentRoot(); + .WithRazorPagesAtContentRoot() + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs index 4ee45cae12..0ded866ccf 100644 --- a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs +++ b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using RazorPagesWebSite.Conventions; @@ -24,7 +25,8 @@ namespace RazorPagesWebSite options.Conventions.AuthorizeAreaFolder("Accounts", "/RequiresAuth"); options.Conventions.AllowAnonymousToAreaPage("Accounts", "/RequiresAuth/AllowAnonymous"); options.Conventions.Add(new CustomModelTypeConvention()); - }); + }) + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs b/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs index ca653f83a0..d170e5cf57 100644 --- a/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs +++ b/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs @@ -7,6 +7,8 @@ namespace RazorWebSite.Controllers { public class EmbeddedViewsController : Controller { + public IActionResult Index() => null; + public IActionResult LookupByName() => View("Index"); public IActionResult LookupByPath() => View("/Views/EmbeddedViews/Index.cshtml"); diff --git a/test/WebSites/RazorWebSite/Startup.cs b/test/WebSites/RazorWebSite/Startup.cs index d0a4244558..c4b02e356f 100644 --- a/test/WebSites/RazorWebSite/Startup.cs +++ b/test/WebSites/RazorWebSite/Startup.cs @@ -6,6 +6,7 @@ using System.Globalization; using System.Reflection; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Localization; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.DependencyInjection; @@ -41,7 +42,8 @@ namespace RazorWebSite options.HtmlHelperOptions.ValidationMessageElement = "validationMessageElement"; options.HtmlHelperOptions.ValidationSummaryMessageElement = "validationSummaryElement"; }) - .AddMvcLocalization(LanguageViewLocationExpanderFormat.SubFolder); + .AddMvcLocalization(LanguageViewLocationExpanderFormat.SubFolder) + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddTransient(); services.AddTransient(); diff --git a/test/WebSites/RazorWebSite/StartupDataAnnotations.cs b/test/WebSites/RazorWebSite/StartupDataAnnotations.cs index 7ec35e86fa..d3159ea501 100644 --- a/test/WebSites/RazorWebSite/StartupDataAnnotations.cs +++ b/test/WebSites/RazorWebSite/StartupDataAnnotations.cs @@ -6,7 +6,6 @@ using System.Globalization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Localization; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace RazorWebSite @@ -25,8 +24,8 @@ namespace RazorWebSite { options.DataAnnotationLocalizerProvider = (modelType, stringLocalizerFactory) => stringLocalizerFactory.Create(typeof(SingleType)); - }); - services.Configure(options => options.CompatibilityVersion = CompatibilityVersion.Latest); + }) + .SetCompatibilityVersion(CompatibilityVersion.Latest); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index c7b905bffb..984e4cc5c2 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -1,9 +1,8 @@ // 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; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +13,8 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddScoped(); services.AddSingleton(); @@ -29,8 +29,7 @@ namespace RoutingWebSite "adminRoute", "{area:exists}/{controller}/{action}", new { controller = "Home", action = "Index" }, - new { area = "Travel" } - ); + new { area = "Travel" }); routes.MapRoute( "ActionAsMethod", @@ -43,5 +42,4 @@ namespace RoutingWebSite }); } } -} - +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs similarity index 90% rename from test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs rename to test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 16bc62d81f..33c10a0275 100644 --- a/test/WebSites/RoutingWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -2,18 +2,19 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace RoutingWebSite { - public class StartupWithEndpointRouting + public class StartupWith21Compat { // Set up application services public void ConfigureServices(IServiceCollection services) { services.AddMvc() - .AddMvcOptions(options => options.EnableEndpointRouting = true); + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddScoped(); services.AddSingleton(); diff --git a/test/WebSites/SecurityWebSite/Startup.cs b/test/WebSites/SecurityWebSite/Startup.cs index 7dc96a5c2a..08db0bdd8d 100644 --- a/test/WebSites/SecurityWebSite/Startup.cs +++ b/test/WebSites/SecurityWebSite/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace SecurityWebSite @@ -14,7 +15,8 @@ namespace SecurityWebSite public void ConfigureServices(IServiceCollection services) { // Add framework services. - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddAntiforgery(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => { diff --git a/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs b/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs new file mode 100644 index 0000000000..b674e76fe0 --- /dev/null +++ b/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs @@ -0,0 +1,41 @@ +// 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.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization.Policy; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Authorization; +using Microsoft.Extensions.DependencyInjection; + +namespace SecurityWebSite +{ + public class StartupWith20CompatAndGlobalDenyAnonymousFilter + { + public void ConfigureServices(IServiceCollection services) + { + services + .AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) + .AddCookie(options => + { + options.LoginPath = "/Home/Login"; + options.LogoutPath = "/Home/Logout"; + }).AddCookie("Cookie2"); + + services.AddMvc(o => + { + o.Filters.Add(new AuthorizeFilter()); + }) + .SetCompatibilityVersion(CompatibilityVersion.Version_2_0); + + services.AddScoped(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseAuthentication(); + + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs b/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs index 4fa7b0e884..01c796512f 100644 --- a/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs +++ b/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authorization.Policy; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.Extensions.DependencyInjection; @@ -24,7 +25,8 @@ namespace SecurityWebSite services.AddMvc(o => { o.Filters.Add(new AuthorizeFilter()); - }); + }) + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddScoped(); } diff --git a/test/WebSites/SimpleWebSite/Startup.cs b/test/WebSites/SimpleWebSite/Startup.cs index 50ad06a397..a4264f8abb 100644 --- a/test/WebSites/SimpleWebSite/Startup.cs +++ b/test/WebSites/SimpleWebSite/Startup.cs @@ -2,11 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; -using System.Text.Encodings.Web; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.WebEncoders.Testing; using Microsoft.Net.Http.Headers; using Newtonsoft.Json; @@ -21,7 +20,8 @@ namespace SimpleWebSite .AddMvcCore() .AddAuthorization() .AddFormatterMappings(m => m.SetMediaTypeMappingForFormat("js", new MediaTypeHeaderValue("application/json"))) - .AddJsonFormatters(j => j.Formatting = Formatting.Indented); + .AddJsonFormatters(j => j.Formatting = Formatting.Indented) + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/TagHelpersWebSite/Startup.cs b/test/WebSites/TagHelpersWebSite/Startup.cs index f6c470b7fc..600b2b6c62 100644 --- a/test/WebSites/TagHelpersWebSite/Startup.cs +++ b/test/WebSites/TagHelpersWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace TagHelpersWebSite @@ -13,7 +14,8 @@ namespace TagHelpersWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/VersioningWebSite/Startup.cs b/test/WebSites/VersioningWebSite/Startup.cs index 5373436c06..7a94cb2360 100644 --- a/test/WebSites/VersioningWebSite/Startup.cs +++ b/test/WebSites/VersioningWebSite/Startup.cs @@ -1,9 +1,8 @@ // 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; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -14,7 +13,8 @@ namespace VersioningWebSite public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.AddScoped(); services.AddSingleton(); diff --git a/test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs b/test/WebSites/VersioningWebSite/StartupWith21Compat.cs similarity index 81% rename from test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs rename to test/WebSites/VersioningWebSite/StartupWith21Compat.cs index 9c72888db9..44bbc01aa5 100644 --- a/test/WebSites/VersioningWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/VersioningWebSite/StartupWith21Compat.cs @@ -1,21 +1,20 @@ // 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; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace VersioningWebSite { - public class StartupWithEndpointRouting + public class StartupWith21Compat { public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container services.AddMvc() - .AddMvcOptions(options => options.EnableEndpointRouting = true); + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddScoped(); services.AddSingleton(); diff --git a/test/WebSites/WebApiCompatShimWebSite/Startup.cs b/test/WebSites/WebApiCompatShimWebSite/Startup.cs index 3699ab9e60..b8dda25a9d 100644 --- a/test/WebSites/WebApiCompatShimWebSite/Startup.cs +++ b/test/WebSites/WebApiCompatShimWebSite/Startup.cs @@ -4,6 +4,7 @@ using System.IO; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace WebApiCompatShimWebSite @@ -13,7 +14,10 @@ namespace WebApiCompatShimWebSite public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container - services.AddMvc().AddWebApiConventions(); + services.AddMvc() + .AddWebApiConventions() + .SetCompatibilityVersion(CompatibilityVersion.Latest) + .AddMvcOptions(options => options.EnableEndpointRouting = false); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/XmlFormattersWebSite/Startup.cs b/test/WebSites/XmlFormattersWebSite/Startup.cs index 31bce95f56..07bda1e37a 100644 --- a/test/WebSites/XmlFormattersWebSite/Startup.cs +++ b/test/WebSites/XmlFormattersWebSite/Startup.cs @@ -17,7 +17,8 @@ namespace XmlFormattersWebSite public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container - services.AddMvc(); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); services.Configure(options => { From dfb579d45c7ba9f095b10505be57178897bf13ba Mon Sep 17 00:00:00 2001 From: Javier Calvarro Nelson Date: Tue, 21 Aug 2018 17:01:20 -0700 Subject: [PATCH 189/316] [Fixes #8021] Copy the request headers before sending the request on the RedirectHandler If another handler modifies the request headers the modified headers get applied on subsequent requests, which is not correct. This change copies the headers before sending the request and uses the original headers for the redirect request instead of the potentially modified ones. --- .../Handlers/RedirectHandler.cs | 42 ++++++++++------- .../TestingInfrastructureTests.cs | 47 ++++++++++++++++++- .../Controllers/TestingController.cs | 26 ++++++++++ 3 files changed, 97 insertions(+), 18 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs index 63158713d3..71198d3d75 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs @@ -5,6 +5,7 @@ using System; using System.IO; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; @@ -49,13 +50,14 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) { var remainingRedirects = MaxRedirects; - + var redirectRequest = new HttpRequestMessage(); var originalRequestContent = HasBody(request) ? await DuplicateRequestContent(request) : null; + CopyRequestHeaders(request.Headers, redirectRequest.Headers); var response = await base.SendAsync(request, cancellationToken); while (IsRedirect(response) && remainingRedirects >= 0) { remainingRedirects--; - var redirectRequest = GetRedirectRequest(response, originalRequestContent); + UpdateRedirectRequest(response, redirectRequest, originalRequestContent); originalRequestContent = HasBody(redirectRequest) ? await DuplicateRequestContent(redirectRequest) : null; response = await base.SendAsync(redirectRequest, cancellationToken); } @@ -95,6 +97,16 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers } } + private static void CopyRequestHeaders( + HttpRequestHeaders originalRequestHeaders, + HttpRequestHeaders newRequestHeaders) + { + foreach (var header in originalRequestHeaders) + { + newRequestHeaders.Add(header.Key, header.Value); + } + } + private static async Task<(Stream originalBody, Stream copy)> CopyBody(HttpRequestMessage request) { var originalBody = await request.Content.ReadAsStreamAsync(); @@ -116,8 +128,9 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers return (originalBody, bodyCopy); } - private static HttpRequestMessage GetRedirectRequest( + private static void UpdateRedirectRequest( HttpResponseMessage response, + HttpRequestMessage redirect, HttpContent originalContent) { var location = response.Headers.Location; @@ -128,34 +141,31 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers location); } - var redirect = !ShouldKeepVerb(response) ? - new HttpRequestMessage(HttpMethod.Get, location) : - new HttpRequestMessage(response.RequestMessage.Method, location) - { - Content = originalContent - }; - - foreach (var header in response.RequestMessage.Headers) + redirect.RequestUri = location; + if (!ShouldKeepVerb(response)) { - redirect.Headers.Add(header.Key, header.Value); + redirect.Method = HttpMethod.Get; + } + else + { + redirect.Method = response.RequestMessage.Method; + redirect.Content = originalContent; } foreach (var property in response.RequestMessage.Properties) { redirect.Properties.Add(property.Key, property.Value); } - - return redirect; } private static bool ShouldKeepVerb(HttpResponseMessage response) => response.StatusCode == HttpStatusCode.RedirectKeepVerb || (int)response.StatusCode == 308; - private bool IsRedirect(HttpResponseMessage response) => + private bool IsRedirect(HttpResponseMessage response) => response.StatusCode == HttpStatusCode.MovedPermanently || response.StatusCode == HttpStatusCode.Redirect || response.StatusCode == HttpStatusCode.RedirectKeepVerb || (int)response.StatusCode == 308; } -} +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs index 9e5be478f6..afa57b5092 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs @@ -2,11 +2,13 @@ using System.Net; using System.Net.Http; using System.Net.Http.Formatting; +using System.Threading; using System.Threading.Tasks; using BasicWebSite; using BasicWebSite.Controllers; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; +using Microsoft.AspNetCore.Mvc.Testing.Handlers; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; using Xunit; @@ -17,13 +19,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public TestingInfrastructureTests(WebApplicationFactory fixture) { - var factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); - Client = factory.CreateClient(); + Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(ConfigureWebHostBuilder); + Client = Factory.CreateClient(); } private static void ConfigureWebHostBuilder(IWebHostBuilder builder) => builder.ConfigureTestServices(s => s.AddSingleton()); + public WebApplicationFactory Factory { get; } public HttpClient Client { get; } [Fact] @@ -57,6 +60,22 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(5, handlerResponse.Body); } + [Fact] + public async Task TestingInfrastructure_RedirectHandlerUsesOriginalRequestHeaders() + { + // Act + var request = new HttpRequestMessage(HttpMethod.Get, "Testing/RedirectHandler/Headers"); + var client = Factory.CreateDefaultClient( + new RedirectHandler(), new TestHandler()); + var response = await client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var modifiedHeaderWasSent = await response.Content.ReadAsStringAsync(); + + Assert.Equal("false", modifiedHeaderWasSent); + } + [Fact] public async Task TestingInfrastructure_PostRedirectGetWorksWithCookies() { @@ -93,5 +112,29 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Message = "Test"; } } + + private class TestHandler : DelegatingHandler + { + public TestHandler() + { + } + + public TestHandler(HttpMessageHandler innerHandler) : base(innerHandler) + { + } + + public bool HeaderAdded { get; set; } + + protected override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken) + { + if (!HeaderAdded) + { + request.Headers.Add("X-Added-Header", "true"); + HeaderAdded = true; + } + + return base.SendAsync(request, cancellationToken); + } + } } } diff --git a/test/WebSites/BasicWebSite/Controllers/TestingController.cs b/test/WebSites/BasicWebSite/Controllers/TestingController.cs index 07e76a3d6b..882ed935c9 100644 --- a/test/WebSites/BasicWebSite/Controllers/TestingController.cs +++ b/test/WebSites/BasicWebSite/Controllers/TestingController.cs @@ -37,6 +37,32 @@ namespace BasicWebSite.Controllers return Ok(new RedirectHandlerResponse { Url = value, Body = number.Value }); } + [HttpGet("Testing/RedirectHandler/Headers")] + public IActionResult RedirectHandlerHeaders() + { + if (!Request.Headers.TryGetValue("X-Added-Header", out var value)) + { + return Content("No header present"); + } + else + { + return RedirectToAction(nameof(RedirectHandlerHeadersRedirect)); + } + } + + [HttpGet("Testing/RedirectHandler/Headers/Redirect")] + public IActionResult RedirectHandlerHeadersRedirect() + { + if (Request.Headers.TryGetValue("X-Added-Header", out var value)) + { + return Content("true"); + } + else + { + return Content("false"); + } + } + [HttpGet("Testing/AntiforgerySimulator/{value}")] public IActionResult AntiforgerySimulator([FromRoute]int value) { From a7301120b1971d6d047c63db5e6911aa134f99a2 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 20 Aug 2018 18:51:16 -0700 Subject: [PATCH 190/316] Unwrap filter factories in TypeFilterAttribute & ServiceFilterAttribute Fixes #7855 --- .../ServiceFilterAttribute.cs | 12 +- .../TypeFilterAttribute.cs | 10 +- .../ServiceFilterAttributeTest.cs | 62 ++++++++++ .../TypeFilterAttributeTest.cs | 116 ++++++++++++++++++ 4 files changed, 190 insertions(+), 10 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs index 525d1a82bb..9e16cb4715 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.Extensions.DependencyInjection; @@ -58,14 +57,11 @@ namespace Microsoft.AspNetCore.Mvc throw new ArgumentNullException(nameof(serviceProvider)); } - var service = serviceProvider.GetRequiredService(ServiceType); - - var filter = service as IFilterMetadata; - if (filter == null) + var filter = (IFilterMetadata)serviceProvider.GetRequiredService(ServiceType); + if (filter is IFilterFactory filterFactory) { - throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter( - typeof(ServiceFilterAttribute).Name, - typeof(IFilterMetadata).Name)); + // Unwrap filter factories + filter = filterFactory.CreateInstance(serviceProvider); } return filter; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs index bc5c19802f..a2048512a6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs @@ -73,11 +73,17 @@ namespace Microsoft.AspNetCore.Mvc if (_factory == null) { var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray(); - _factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes); } - return (IFilterMetadata)_factory(serviceProvider, Arguments); + var filter = (IFilterMetadata)_factory(serviceProvider, Arguments); + if (filter is IFilterFactory filterFactory) + { + // Unwrap filter factories + filter = filterFactory.CreateInstance(serviceProvider); + } + + return filter; } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs new file mode 100644 index 0000000000..e21e55018c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs @@ -0,0 +1,62 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class ServiceFilterAttributeTest + { + [Fact] + public void CreateService_GetsFilterFromServiceProvider() + { + // Arrange + var expected = new TestFilter(); + var serviceProvider = new ServiceCollection() + .AddSingleton(expected) + .BuildServiceProvider(); + + var serviceFilter = new ServiceFilterAttribute(typeof(TestFilter)); + + // Act + var filter = serviceFilter.CreateInstance(serviceProvider); + + // Assert + Assert.Same(expected, filter); + } + + [Fact] + public void CreateService_UnwrapsFilterFactory() + { + // Arrange + var serviceProvider = new ServiceCollection() + .AddSingleton(new TestFilterFactory()) + .BuildServiceProvider(); + + var serviceFilter = new ServiceFilterAttribute(typeof(TestFilterFactory)); + + // Act + var filter = serviceFilter.CreateInstance(serviceProvider); + + // Assert + Assert.IsType(filter); + } + + public class TestFilter : IFilterMetadata + { + } + + public class TestFilterFactory : IFilterFactory + { + public bool IsReusable => throw new NotImplementedException(); + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + return new TestFilter(); + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs new file mode 100644 index 0000000000..27d407b837 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs @@ -0,0 +1,116 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc +{ + public class TypeFilterAttributeTest + { + [Fact] + public void CreateService_TypeActivatesImplementationType() + { + // Arrange + var value = "Some value"; + var uri = new Uri("http://www.asp.net"); + var serviceProvider = new ServiceCollection() + .AddSingleton(value) + .AddSingleton(uri) + .BuildServiceProvider(); + + var typeFilter = new TypeFilterAttribute(typeof(TestFilter)); + + // Act + var filter = typeFilter.CreateInstance(serviceProvider); + + // Assert + var testFilter = Assert.IsType(filter); + Assert.Same(value, testFilter.Value); + Assert.Same(uri, testFilter.Uri); + } + + [Fact] + public void CreateService_UsesArguments() + { + // Arrange + var value = "Some value"; + var uri = new Uri("http://www.asp.net"); + var serviceProvider = new ServiceCollection() + .AddSingleton("Value in DI") + .AddSingleton(uri) + .BuildServiceProvider(); + + var typeFilter = new TypeFilterAttribute(typeof(TestFilter)) + { + Arguments = new[] { value, } + }; + + // Act + var filter = typeFilter.CreateInstance(serviceProvider); + + // Assert + var testFilter = Assert.IsType(filter); + Assert.Same(value, testFilter.Value); + Assert.Same(uri, testFilter.Uri); + } + + [Fact] + public void CreateService_UnwrapsFilterFactory() + { + // Arrange + var value = "Some value"; + var uri = new Uri("http://www.asp.net"); + var serviceProvider = new ServiceCollection() + .AddSingleton("Value in DI") + .AddSingleton(uri) + .BuildServiceProvider(); + + var typeFilter = new TypeFilterAttribute(typeof(TestFilterFactory)) + { + Arguments = new[] { value, } + }; + + // Act + var filter = typeFilter.CreateInstance(serviceProvider); + + // Assert + var testFilter = Assert.IsType(filter); + Assert.Same(value, testFilter.Value); + Assert.Same(uri, testFilter.Uri); + } + + public class TestFilter : IFilterMetadata + { + public TestFilter(string value, Uri uri) + { + Value = value; + Uri = uri; + } + + public string Value { get; } + public Uri Uri { get; } + } + + public class TestFilterFactory : IFilterFactory + { + private readonly string _value; + private readonly Uri _uri; + + public TestFilterFactory(string value, Uri uri) + { + _value = value; + _uri = uri; + } + + public bool IsReusable => throw new NotImplementedException(); + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + return new TestFilter(_value, _uri); + } + } + } +} From cb0627b28aefe94e7f06d297f73d64aa78380177 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 25 Aug 2018 21:25:25 -0700 Subject: [PATCH 191/316] Addressing a skipped test I think something that was meant to be revisited in a PR got left behing. I found this while fixing aspnet/Routing#772 and it seems worth addressing. This change removes the hardcoding of action/controller/area in the data source, and corrects the behavior of required route values when they aren't in that set. --- .../Internal/MvcEndpointDataSource.cs | 161 +++++++++--------- .../Internal/MvcEndpointDataSourceTests.cs | 53 ++++-- 2 files changed, 127 insertions(+), 87 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 128e699774..2837e6ec4a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -89,73 +89,88 @@ namespace Microsoft.AspNetCore.Mvc.Internal // - Home/Login foreach (var endpointInfo in ConventionalEndpointInfos) { - if (MatchRouteValue(action, endpointInfo, "Area") - && MatchRouteValue(action, endpointInfo, "Controller") - && MatchRouteValue(action, endpointInfo, "Action")) + // An 'endpointInfo' is applicable if: + // 1. it has a parameter (or default value) for 'required' non-null route value + // 2. it does not have a parameter (or default value) for 'required' null route value + var isApplicable = true; + foreach (var routeKey in action.RouteValues.Keys) { - var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); - - for (var i = 0; i < newPathSegments.Count; i++) + if (!MatchRouteValue(action, endpointInfo, routeKey)) { - // Check if the pattern can be shortened because the remaining parameters are optional - // - // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index - // can resolve to the following endpoints: - // - /Home/Index/{id?} - // - /Home - // - / - if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) + isApplicable = false; + break; + } + } + + if (!isApplicable) + { + continue; + } + + var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); + + for (var i = 0; i < newPathSegments.Count; i++) + { + // Check if the pattern can be shortened because the remaining parameters are optional + // + // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index + // can resolve to the following endpoints: + // - /Home/Index/{id?} + // - /Home + // - / + if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) + { + var subPathSegments = newPathSegments.Take(i); + + var subEndpoint = CreateEndpoint( + action, + endpointInfo.Name, + GetPattern(ref patternStringBuilder, subPathSegments), + subPathSegments, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo, + suppressLinkGeneration: false); + endpoints.Add(subEndpoint); + } + + List segmentParts = null; // Initialize only as needed + var segment = newPathSegments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + + if (part.IsParameter && + part is RoutePatternParameterPart parameterPart && + action.RouteValues.ContainsKey(parameterPart.Name)) { - var subPathSegments = newPathSegments.Take(i); - - var subEndpoint = CreateEndpoint( - action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, subPathSegments), - subPathSegments, - endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, - suppressLinkGeneration: false); - endpoints.Add(subEndpoint); - } - - List segmentParts = null; // Initialize only as needed - var segment = newPathSegments[i]; - for (var j = 0; j < segment.Parts.Count; j++) - { - var part = segment.Parts[j]; - - if (part.IsParameter && part is RoutePatternParameterPart parameterPart && IsMvcParameter(parameterPart.Name)) + if (segmentParts == null) { - if (segmentParts == null) - { - segmentParts = segment.Parts.ToList(); - } - - // Replace parameter with literal value - segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); + segmentParts = segment.Parts.ToList(); } - } - // A parameter part was replaced so replace segment with updated parts - if (segmentParts != null) - { - newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + // Replace parameter with literal value + segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); } } - var endpoint = CreateEndpoint( - action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, newPathSegments), - newPathSegments, - endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, - suppressLinkGeneration: false); - endpoints.Add(endpoint); + // A parameter part was replaced so replace segment with updated parts + if (segmentParts != null) + { + newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + } } + + var endpoint = CreateEndpoint( + action, + endpointInfo.Name, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo, + suppressLinkGeneration: false); + endpoints.Add(endpoint); } } else @@ -190,18 +205,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private bool IsMvcParameter(string name) - { - if (string.Equals(name, "Area", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Controller", StringComparison.OrdinalIgnoreCase) - || string.Equals(name, "Action", StringComparison.OrdinalIgnoreCase)) - { - return true; - } - - return false; - } - private bool UseDefaultValuePlusRemainingSegementsOptional( int segmentIndex, ActionDescriptor action, @@ -225,7 +228,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal continue; } - if (IsMvcParameter(parameterPart.Name)) + if (action.RouteValues.ContainsKey(parameterPart.Name)) { if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue && action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) @@ -266,8 +269,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Action does not have a value for this routeKey, most likely because action is not in an area // Check that the pattern does not have a parameter for the routeKey - var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); - if (matchingParameter == null) + var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey); + if (matchingParameter == null && + (!endpointInfo.ParsedPattern.Defaults.TryGetValue(routeKey, out var value) || + !string.IsNullOrEmpty(Convert.ToString(value)))) { return true; } @@ -279,7 +284,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return true; } - var matchingParameter = endpointInfo.ParsedPattern.Parameters.SingleOrDefault(p => string.Equals(p.Name, routeKey, StringComparison.OrdinalIgnoreCase)); + var matchingParameter = endpointInfo.ParsedPattern.GetParameter(routeKey); if (matchingParameter != null) { // Check that the value matches against constraints on that parameter @@ -358,10 +363,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal object source, bool suppressLinkGeneration) { - var metadata = new List(); - // REVIEW: Used for debugging. Consider removing before release - metadata.Add(source); - metadata.Add(action); + var metadata = new List + { + // REVIEW: Used for debugging. Consider removing before release + source, + action + }; if (action.EndpointMetadata != null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index fbee5e6f55..e4230b1d56 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -511,29 +511,40 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Empty(endpoints); } - // Since area, controller, action and page are special, check to see if the followin test succeeds for a - // custom required value too. - [Fact(Skip = "Needs review")] + // area, controller, action and page are special, but not hardcoded. Actions can define custom required + // route values. This has been used successfully for localization, versioning and similar schemes. We should + // be able to replace custom route values too. + [Fact] public void NonReservedRequiredValue_WithNoCorresponding_TemplateParameter_DoesNotProduceEndpoint() { // Arrange - var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index", foo = "bar" }); - var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); + var action1 = new RouteValueDictionary(new { controller = "home", action = "index", locale = "en-NZ" }); + var action2 = new RouteValueDictionary(new { controller = "home", action = "about", locale = "en-CA" }); + var action3 = new RouteValueDictionary(new { controller = "home", action = "index", locale = (string)null }); + + var actionDescriptorCollection = GetActionDescriptorCollection(action1, action2, action3); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Adding a localized route a non-localized route + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{locale}/{controller}/{action}")); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{controller}/{action}")); // Act var endpoints = dataSource.Endpoints; // Assert - Assert.Empty(endpoints); + Assert.Collection( + endpoints.Cast().OrderBy(e => e.RoutePattern.RawText), + e => Assert.Equal("en-CA/home/about", e.RoutePattern.RawText), + e => Assert.Equal("en-NZ/home/index", e.RoutePattern.RawText), + e => Assert.Equal("home/index", e.RoutePattern.RawText)); } [Fact] public void TemplateParameter_WithNoDefaultOrRequiredValue_DoesNotProduceEndpoint() { // Arrange - var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index" }); + var requiredValues = new RouteValueDictionary(new { controller = "home", action = "index", area = (string)null }); var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, "{area}/{controller}/{action}")); @@ -606,7 +617,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add( - CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); + CreateEndpointInfo(string.Empty, "{subarea}/{controller=Home}/{action=Index}")); // Act var endpoints = dataSource.Endpoints; @@ -614,10 +625,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert var endpoint = Assert.Single(endpoints); var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("test/Foo/Bar", matcherEndpoint.RoutePattern.RawText); AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } + [Fact] + public void RequiredValues_NotPresent_InDefaultValuesOrParameter_EndpointNotCreated() + { + // Arrange + var requiredValues = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", subarea = "test" }); + var expectedDefaults = requiredValues; + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Empty(endpoints); + } + [Fact] public void RequiredValues_IsSubsetOf_DefaultValues() { @@ -629,7 +659,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add( - CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}/{subscription=general}")); + CreateEndpointInfo( + string.Empty, + "{controller=Home}/{action=Index}/{subscription=general}", + defaults: new RouteValueDictionary(new { subarea = "test", }))); // Act var endpoints = dataSource.Endpoints; From a634f6b116ceeef5c36d928ec4e15ac5d01cd75b Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 25 Aug 2018 21:43:06 -0700 Subject: [PATCH 192/316] add another test --- .../Internal/MvcEndpointDataSourceTests.cs | 56 ++++++++++++++++++- 1 file changed, 55 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index e4230b1d56..416e359291 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -656,7 +656,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal new { controller = "Foo", action = "Bar", subarea = "test" }); var expectedDefaults = new RouteValueDictionary( new { controller = "Foo", action = "Bar", subarea = "test", subscription = "general" }); - var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); + var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); dataSource.ConventionalEndpointInfos.Add( CreateEndpointInfo( @@ -674,6 +674,60 @@ namespace Microsoft.AspNetCore.Mvc.Internal AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } + [Fact] + public void RequiredValues_DoesNotMatchParameterDefaults_Included() + { + // Arrange + var action = new RouteValueDictionary( + new { controller = "Foo", action = "Baz", }); // Doesn't match default + var expectedDefaults = new RouteValueDictionary( + new { controller = "Foo", action = "Baz", }); + var actionDescriptorCollection = GetActionDescriptorCollection(action); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo( + string.Empty, + "{controller}/{action}/{id?}", + defaults: new RouteValueDictionary(new { controller = "Foo", action = "Bar" }))); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + var endpoint = Assert.Single(endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Foo/Baz/{id?}", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); + } + + [Fact] + public void RequiredValues_DoesNotMatchNonParameterDefaults_FilteredOut() + { + // Arrange + var action1 = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", }); + var action2 = new RouteValueDictionary( + new { controller = "Foo", action = "Baz", }); // Doesn't match default + var expectedDefaults = new RouteValueDictionary( + new { controller = "Foo", action = "Bar", }); + var actionDescriptorCollection = GetActionDescriptorCollection(action1, action2); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add( + CreateEndpointInfo( + string.Empty, + "Blog/{*slug}", + defaults: new RouteValueDictionary(new { controller = "Foo", action = "Bar" }))); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + var endpoint = Assert.Single(endpoints); + var matcherEndpoint = Assert.IsType(endpoint); + Assert.Equal("Blog/{*slug}", matcherEndpoint.RoutePattern.RawText); + AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); + } + [Fact] public void RequiredValues_HavingNull_AndNotPresentInDefaultValues_IsAddedToDefaultValues() { From 8ed9d0aac223e2566af4334b6293624def5e7cd5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 28 Aug 2018 11:00:11 +1200 Subject: [PATCH 193/316] Use Endpoint instead of RouteEndpoint where possible (#8331) --- build/dependencies.props | 146 +++++++++--------- .../Routing/ConsumesMatcherPolicy.cs | 4 +- .../ActionConstraintMatcherPolicyTest.cs | 6 +- 3 files changed, 76 insertions(+), 80 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d6804c2325..b0c3540479 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 2.2.0-preview1-20180807.2 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-a-preview2-routepattern-defaults-16901 - 2.2.0-a-preview2-routepattern-defaults-16901 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 5.2.6 2.8.0 2.8.0 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 1.7.0 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 2.1.0 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview1-34967 - 2.2.0-preview1-34967 + 2.2.0-preview2-35090 + 2.2.0-preview2-35090 15.6.1 4.7.49 2.0.3 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs index 507d3bcf68..65dd1d117b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -135,14 +135,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing private Endpoint CreateRejectionEndpoint() { - return new RouteEndpoint( + return new Endpoint( (context) => { context.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; return Task.CompletedTask; }, - RoutePatternFactory.Parse("/"), - 0, EndpointMetadataCollection.Empty, Http415EndpointDisplayName); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index e2f02e7f63..298786c121 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -370,13 +370,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing return httpContext; } - private static RouteEndpoint CreateEndpoint(ActionDescriptor action) + private static Endpoint CreateEndpoint(ActionDescriptor action) { var metadata = new List() { action, }; - return new RouteEndpoint( + return new Endpoint( (context) => Task.CompletedTask, - RoutePatternFactory.Parse("/"), - 0, new EndpointMetadataCollection(metadata), $"test: {action?.DisplayName}"); } From 0fcf2448c3d8f92a018927dfdaec42fc869c73db Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 25 Aug 2018 19:42:32 -0700 Subject: [PATCH 194/316] Fix aspnet/Routing#722 Exposes a separate change token that will be triggered after action descriptors have been updated. --- .../MvcEndpointDatasourceBenchmark.cs | 1 - .../MvcCoreServiceCollectionExtensions.cs | 2 +- .../ActionDescriptorCollectionProvider.cs | 33 ++ ...faultActionDescriptorCollectionProvider.cs | 160 +++++++++ .../IActionDescriptorChangeProvider.cs | 11 + .../IActionDescriptorCollectionProvider.cs | 12 +- .../ActionDescriptorCollectionProvider.cs | 95 ----- .../Internal/MvcEndpointDataSource.cs | 338 +++++++++--------- ...ActionDescriptorCollectionProviderTest.cs} | 88 ++--- .../Internal/ActionConstraintCacheTest.cs | 2 +- .../Internal/ActionSelectorTest.cs | 4 +- .../Internal/MvcEndpointDataSourceTests.cs | 75 +--- .../ActionConstraintMatcherPolicyTest.cs | 4 +- .../Routing/KnownRouteValueConstraintTests.cs | 2 +- .../ApiExplorerTest.cs | 2 +- 15 files changed, 448 insertions(+), 381 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/{Internal/ActionDescriptorCollectionProviderTest.cs => Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs} (70%) diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs index 85bf14c1cf..893e8c8df8 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -111,7 +111,6 @@ namespace Microsoft.AspNetCore.Mvc.Performance var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - Array.Empty(), new MockServiceProvider()); return dataSource; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 33d7e6aa93..767aa0c33f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -165,7 +165,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddEnumerable( ServiceDescriptor.Transient()); - services.TryAddSingleton(); + services.TryAddSingleton(); // // Action Selection diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs new file mode 100644 index 0000000000..633890e08f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs @@ -0,0 +1,33 @@ +// 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.Abstractions; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// A base class for which also provides an + /// for reactive notifications of changes. + /// + /// + /// is used as a base class by the default implementation of + /// . To retrieve an instance of , + /// obtain the from the dependency injection provider and + /// downcast to . + /// + public abstract class ActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider + { + /// + /// Returns the current cached + /// + public abstract ActionDescriptorCollection ActionDescriptors { get; } + + /// + /// Gets an that will be signaled after the + /// collection has changed. + /// + /// The . + public abstract IChangeToken GetChangeToken(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs new file mode 100644 index 0000000000..6204ce3945 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs @@ -0,0 +1,160 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.Extensions.Primitives; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal class DefaultActionDescriptorCollectionProvider : ActionDescriptorCollectionProvider + { + private readonly IActionDescriptorProvider[] _actionDescriptorProviders; + private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; + + // The lock is used to protect WRITES to the following (do not need to protect reads once initialized). + private readonly object _lock; + private ActionDescriptorCollection _collection; + private IChangeToken _changeToken; + private CancellationTokenSource _cancellationTokenSource; + private int _version = 0; + + public DefaultActionDescriptorCollectionProvider( + IEnumerable actionDescriptorProviders, + IEnumerable actionDescriptorChangeProviders) + { + _actionDescriptorProviders = actionDescriptorProviders + .OrderBy(p => p.Order) + .ToArray(); + + _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); + + ChangeToken.OnChange( + GetCompositeChangeToken, + UpdateCollection); + + _lock = new object(); + } + + /// + /// Returns a cached collection of . + /// + public override ActionDescriptorCollection ActionDescriptors + { + get + { + Initialize(); + Debug.Assert(_collection != null); + Debug.Assert(_changeToken != null); + + return _collection; + } + } + + /// + /// Gets an that will be signaled after the + /// collection has changed. + /// + /// The . + public override IChangeToken GetChangeToken() + { + Initialize(); + Debug.Assert(_collection != null); + Debug.Assert(_changeToken != null); + + return _changeToken; + } + + private IChangeToken GetCompositeChangeToken() + { + if (_actionDescriptorChangeProviders.Length == 1) + { + return _actionDescriptorChangeProviders[0].GetChangeToken(); + } + + var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length]; + for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++) + { + changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken(); + } + + return new CompositeChangeToken(changeTokens); + } + + private void Initialize() + { + // Using double-checked locking on initialization because we fire change token callbacks + // when the collection changes. We don't want to do that repeatedly for redundant changes. + // + // The main call path of this code on the first call is async initialization from Endpoint Routing + // which is done in a non-blocking way so in practice no caller will ever block here. + if (_collection == null) + { + lock (_lock) + { + if (_collection == null) + { + UpdateCollection(); + } + } + } + } + + private void UpdateCollection() + { + // Using the lock to initialize writes means that we serialize changes. This eliminates + // the potential for changes to be processed out of order - the risk is that newer data + // could be overwritten by older data. + lock (_lock) + { + var context = new ActionDescriptorProviderContext(); + + for (var i = 0; i < _actionDescriptorProviders.Length; i++) + { + _actionDescriptorProviders[i].OnProvidersExecuting(context); + } + + for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--) + { + _actionDescriptorProviders[i].OnProvidersExecuted(context); + } + + // The sequence for an update is important because we don't want anyone to obtain + // the new change token but the old action descriptor collection. + // 1. Obtain the old cancellation token source (don't trigger it yet) + // 2. Set the new action descriptor collection + // 3. Set the new change token + // 4. Trigger the old cancellation token source + // + // Consumers who poll will observe a new action descriptor collection at step 2 - they will see + // the new collection and ignore the change token. + // + // Consumers who listen to the change token will requery at step 4 - they will see the new collection + // and new change token. + // + // Anyone who acquires the collection and change token between steps 2 and 3 will be notified of + // a no-op change at step 4. + + // Step 1. + var oldCancellationTokenSource = _cancellationTokenSource; + + // Step 2. + _collection = new ActionDescriptorCollection( + new ReadOnlyCollection(context.Results), + _version++); + + // Step 3. + _cancellationTokenSource = new CancellationTokenSource(); + _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); + + // Step 4 - might be null if it's the first time. + oldCancellationTokenSource?.Cancel(); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs index d736512f1c..7b524dd10c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs @@ -1,6 +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.Abstractions; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Infrastructure @@ -9,6 +10,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// Provides a way to signal invalidation of the cached collection of from an /// . /// + /// + /// The change token returned from is only for use inside the MVC infrastructure. + /// Use to be notified of + /// changes. + /// public interface IActionDescriptorChangeProvider { /// @@ -16,6 +22,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// instances. /// /// The . + /// + /// The change token returned from is only for use inside the MVC infrastructure. + /// Use to be notified of + /// changes. + /// IChangeToken GetChangeToken(); } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs index 3e64a66677..ee1648e170 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs @@ -1,18 +1,28 @@ // 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.Extensions.Primitives; + namespace Microsoft.AspNetCore.Mvc.Infrastructure { /// /// Provides the currently cached collection of . /// /// + /// /// The default implementation internally caches the collection and uses /// to invalidate this cache, incrementing /// the collection is reconstructed. - /// + /// + /// + /// To be reactively notified of changes, downcast to and + /// subcribe to the change token returned from + /// using . + /// + /// /// Default consumers of this service, are aware of the version and will recache /// data as appropriate, but rely on the version being unique. + /// /// public interface IActionDescriptorCollectionProvider { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs deleted file mode 100644 index 49cb219178..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionDescriptorCollectionProvider.cs +++ /dev/null @@ -1,95 +0,0 @@ -// 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.Collections.ObjectModel; -using System.Linq; -using System.Threading; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.Extensions.Primitives; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - /// - /// Default implementation of . - /// - public class ActionDescriptorCollectionProvider : IActionDescriptorCollectionProvider - { - private readonly IActionDescriptorProvider[] _actionDescriptorProviders; - private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; - private ActionDescriptorCollection _collection; - private int _version = -1; - - /// - /// Initializes a new instance of the class. - /// - /// The sequence of . - /// The sequence of . - public ActionDescriptorCollectionProvider( - IEnumerable actionDescriptorProviders, - IEnumerable actionDescriptorChangeProviders) - { - _actionDescriptorProviders = actionDescriptorProviders - .OrderBy(p => p.Order) - .ToArray(); - - _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); - - ChangeToken.OnChange( - GetCompositeChangeToken, - UpdateCollection); - } - - private IChangeToken GetCompositeChangeToken() - { - if (_actionDescriptorChangeProviders.Length == 1) - { - return _actionDescriptorChangeProviders[0].GetChangeToken(); - } - - var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length]; - for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++) - { - changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken(); - } - - return new CompositeChangeToken(changeTokens); - } - - /// - /// Returns a cached collection of . - /// - public ActionDescriptorCollection ActionDescriptors - { - get - { - if (_collection == null) - { - UpdateCollection(); - } - - return _collection; - } - } - - private void UpdateCollection() - { - var context = new ActionDescriptorProviderContext(); - - for (var i = 0; i < _actionDescriptorProviders.Length; i++) - { - _actionDescriptorProviders[i].OnProvidersExecuting(context); - } - - for (var i = _actionDescriptorProviders.Length - 1; i >= 0; i--) - { - _actionDescriptorProviders[i].OnProvidersExecuted(context); - } - - _collection = new ActionDescriptorCollection( - new ReadOnlyCollection(context.Results), - Interlocked.Increment(ref _version)); - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 2837e6ec4a..560016a4c4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -3,8 +3,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; +using System.Threading; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; @@ -13,25 +15,27 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Patterns; -using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.Internal { internal class MvcEndpointDataSource : EndpointDataSource { - private readonly object _lock = new object(); private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; private readonly DefaultHttpContext _httpContextInstance; - private readonly IActionDescriptorChangeProvider[] _actionDescriptorChangeProviders; + // The following are protected by this lock for WRITES only. This pattern is similar + // to DefaultActionDescriptorChangeProvider - see comments there for details on + // all of the threading behaviors. + private readonly object _lock = new object(); private List _endpoints; + private CancellationTokenSource _cancellationTokenSource; + private IChangeToken _changeToken; public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, MvcEndpointInvokerFactory invokerFactory, - IEnumerable actionDescriptorChangeProviders, IServiceProvider serviceProvider) { if (actions == null) @@ -44,11 +48,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(invokerFactory)); } - if (actionDescriptorChangeProviders == null) - { - throw new ArgumentNullException(nameof(actionDescriptorChangeProviders)); - } - if (serviceProvider == null) { throw new ArgumentNullException(nameof(serviceProvider)); @@ -56,139 +55,199 @@ namespace Microsoft.AspNetCore.Mvc.Internal _actions = actions; _invokerFactory = invokerFactory; - _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); _httpContextInstance = new DefaultHttpContext() { RequestServices = serviceProvider }; ConventionalEndpointInfos = new List(); + + // It's possible for someone to override the collection provider without providing + // change notifications. If that's the case we won't process changes. + if (actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken) + { + ChangeToken.OnChange( + () => collectionProviderWithChangeToken.GetChangeToken(), + UpdateEndpoints); + } } - private List CreateEndpoints() + public List ConventionalEndpointInfos { get; } + + public override IReadOnlyList Endpoints { - var endpoints = new List(); - StringBuilder patternStringBuilder = null; - - foreach (var action in _actions.ActionDescriptors.Items) + get { - if (action.AttributeRouteInfo == null) + Initialize(); + Debug.Assert(_changeToken != null); + Debug.Assert(_endpoints != null); + return _endpoints; + } + } + + public override IChangeToken GetChangeToken() + { + Initialize(); + Debug.Assert(_changeToken != null); + Debug.Assert(_endpoints != null); + return _changeToken; + } + + private void Initialize() + { + if (_endpoints == null) + { + lock (_lock) { - // In traditional conventional routing setup, the routes defined by a user have a static order - // defined by how they are added into the list. We would like to maintain the same order when building - // up the endpoints too. - // - // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. - // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. - var conventionalRouteOrder = 0; - - // Check each of the conventional patterns to see if the action would be reachable - // If the action and pattern are compatible then create an endpoint with the - // area/controller/action parameter parts replaced with literals - // - // e.g. {controller}/{action} with HomeController.Index and HomeController.Login - // would result in endpoints: - // - Home/Index - // - Home/Login - foreach (var endpointInfo in ConventionalEndpointInfos) + if (_endpoints == null) { - // An 'endpointInfo' is applicable if: - // 1. it has a parameter (or default value) for 'required' non-null route value - // 2. it does not have a parameter (or default value) for 'required' null route value - var isApplicable = true; - foreach (var routeKey in action.RouteValues.Keys) + UpdateEndpoints(); + } + } + } + } + + private void UpdateEndpoints() + { + lock (_lock) + { + var endpoints = new List(); + StringBuilder patternStringBuilder = null; + + foreach (var action in _actions.ActionDescriptors.Items) + { + if (action.AttributeRouteInfo == null) + { + // In traditional conventional routing setup, the routes defined by a user have a static order + // defined by how they are added into the list. We would like to maintain the same order when building + // up the endpoints too. + // + // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. + // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. + var conventionalRouteOrder = 0; + + // Check each of the conventional patterns to see if the action would be reachable + // If the action and pattern are compatible then create an endpoint with the + // area/controller/action parameter parts replaced with literals + // + // e.g. {controller}/{action} with HomeController.Index and HomeController.Login + // would result in endpoints: + // - Home/Index + // - Home/Login + foreach (var endpointInfo in ConventionalEndpointInfos) { - if (!MatchRouteValue(action, endpointInfo, routeKey)) + // An 'endpointInfo' is applicable if: + // 1. it has a parameter (or default value) for 'required' non-null route value + // 2. it does not have a parameter (or default value) for 'required' null route value + var isApplicable = true; + foreach (var routeKey in action.RouteValues.Keys) { - isApplicable = false; - break; - } - } - - if (!isApplicable) - { - continue; - } - - var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); - - for (var i = 0; i < newPathSegments.Count; i++) - { - // Check if the pattern can be shortened because the remaining parameters are optional - // - // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index - // can resolve to the following endpoints: - // - /Home/Index/{id?} - // - /Home - // - / - if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) - { - var subPathSegments = newPathSegments.Take(i); - - var subEndpoint = CreateEndpoint( - action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, subPathSegments), - subPathSegments, - endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, - suppressLinkGeneration: false); - endpoints.Add(subEndpoint); - } - - List segmentParts = null; // Initialize only as needed - var segment = newPathSegments[i]; - for (var j = 0; j < segment.Parts.Count; j++) - { - var part = segment.Parts[j]; - - if (part.IsParameter && - part is RoutePatternParameterPart parameterPart && - action.RouteValues.ContainsKey(parameterPart.Name)) + if (!MatchRouteValue(action, endpointInfo, routeKey)) { - if (segmentParts == null) - { - segmentParts = segment.Parts.ToList(); - } - - // Replace parameter with literal value - segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); + isApplicable = false; + break; } } - // A parameter part was replaced so replace segment with updated parts - if (segmentParts != null) + if (!isApplicable) { - newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + continue; } - } + var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); + + for (var i = 0; i < newPathSegments.Count; i++) + { + // Check if the pattern can be shortened because the remaining parameters are optional + // + // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index + // can resolve to the following endpoints: + // - /Home/Index/{id?} + // - /Home + // - / + if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) + { + var subPathSegments = newPathSegments.Take(i); + + var subEndpoint = CreateEndpoint( + action, + endpointInfo.Name, + GetPattern(ref patternStringBuilder, subPathSegments), + subPathSegments, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo, + suppressLinkGeneration: false); + endpoints.Add(subEndpoint); + } + + List segmentParts = null; // Initialize only as needed + var segment = newPathSegments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + + if (part.IsParameter && + part is RoutePatternParameterPart parameterPart && + action.RouteValues.ContainsKey(parameterPart.Name)) + { + if (segmentParts == null) + { + segmentParts = segment.Parts.ToList(); + } + + // Replace parameter with literal value + segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); + } + } + + // A parameter part was replaced so replace segment with updated parts + if (segmentParts != null) + { + newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + } + } + + var endpoint = CreateEndpoint( + action, + endpointInfo.Name, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, + endpointInfo.Defaults, + ++conventionalRouteOrder, + endpointInfo, + suppressLinkGeneration: false); + endpoints.Add(endpoint); + } + } + else + { var endpoint = CreateEndpoint( action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, newPathSegments), - newPathSegments, - endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, - suppressLinkGeneration: false); + action.AttributeRouteInfo.Name, + action.AttributeRouteInfo.Template, + RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, + nonInlineDefaults: null, + action.AttributeRouteInfo.Order, + action.AttributeRouteInfo, + suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration); endpoints.Add(endpoint); } } - else - { - var endpoint = CreateEndpoint( - action, - action.AttributeRouteInfo.Name, - action.AttributeRouteInfo.Template, - RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, - nonInlineDefaults: null, - action.AttributeRouteInfo.Order, - action.AttributeRouteInfo, - suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration); - endpoints.Add(endpoint); - } - } - return endpoints; + // See comments in DefaultActionDescriptorCollectionProvider. These steps are done + // in a specific order to ensure callers always see a consistent state. + + // Step 1 - capture old token + var oldCancellationTokenSource = _cancellationTokenSource; + + // Step 2 - update endpoints + _endpoints = endpoints; + + // Step 3 - create new change token + _cancellationTokenSource = new CancellationTokenSource(); + _changeToken = new CancellationChangeToken(_cancellationTokenSource.Token); + + // Step 4 - trigger old token + oldCancellationTokenSource?.Cancel(); + } string GetPattern(ref StringBuilder sb, IEnumerable segments) { @@ -442,49 +501,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private IChangeToken GetCompositeChangeToken() - { - if (_actionDescriptorChangeProviders.Length == 1) - { - return _actionDescriptorChangeProviders[0].GetChangeToken(); - } - - var changeTokens = new IChangeToken[_actionDescriptorChangeProviders.Length]; - for (var i = 0; i < _actionDescriptorChangeProviders.Length; i++) - { - changeTokens[i] = _actionDescriptorChangeProviders[i].GetChangeToken(); - } - - return new CompositeChangeToken(changeTokens); - } - - public override IChangeToken GetChangeToken() => NullChangeToken.Singleton; - - public override IReadOnlyList Endpoints - { - get - { - // Want to initialize endpoints once and then cache while ensuring a null collection is never returned - // Local copy for thread safety + double check locking - var localEndpoints = _endpoints; - if (localEndpoints == null) - { - lock (_lock) - { - localEndpoints = _endpoints; - if (localEndpoints == null) - { - _endpoints = localEndpoints = CreateEndpoints(); - } - } - } - - return localEndpoints; - } - } - - public List ConventionalEndpointInfos { get; } - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionDescriptorCollectionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs similarity index 70% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionDescriptorCollectionProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs index 0c599b47c6..cac4f58aec 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionDescriptorCollectionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs @@ -4,14 +4,13 @@ using System.Linq; using System.Threading; using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Primitives; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc.Infrastructure { - public class ActionDescriptorCollectionProviderTest + public class DefaultActionDescriptorCollectionProviderTest { [Fact] public void ActionDescriptors_ReadsDescriptorsFromActionDescriptorProviders() @@ -24,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var expected3 = new ActionDescriptor(); var actionDescriptorProvider2 = GetActionDescriptorProvider(expected2, expected3); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider1, actionDescriptorProvider2 }, Enumerable.Empty()); @@ -46,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Arrange var actionDescriptorProvider = GetActionDescriptorProvider(new ActionDescriptor()); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider }, Enumerable.Empty()); @@ -66,55 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void ActionDescriptors_UpdateWhenChangeTokenProviderChanges() - { - // Arrange - var actionDescriptorProvider = new Mock(); - var expected1 = new ActionDescriptor(); - var expected2 = new ActionDescriptor(); - - var invocations = 0; - actionDescriptorProvider - .Setup(p => p.OnProvidersExecuting(It.IsAny())) - .Callback((ActionDescriptorProviderContext context) => - { - if (invocations == 0) - { - context.Results.Add(expected1); - } - else - { - context.Results.Add(expected2); - } - - invocations++; - }); - var changeProvider = new TestChangeProvider(); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( - new[] { actionDescriptorProvider.Object }, - new[] { changeProvider }); - - // Act - 1 - var collection1 = actionDescriptorCollectionProvider.ActionDescriptors; - - // Assert - 1 - Assert.Equal(0, collection1.Version); - Assert.Collection(collection1.Items, - item => Assert.Same(expected1, item)); - - // Act - 2 - changeProvider.TokenSource.Cancel(); - var collection2 = actionDescriptorCollectionProvider.ActionDescriptors; - - // Assert - 2 - Assert.NotSame(collection1, collection2); - Assert.Equal(1, collection2.Version); - Assert.Collection(collection2.Items, - item => Assert.Same(expected2, item)); - } - - [Fact] - public void ActionDescriptors_SubscribesToNewChangeNotificationsAfterInvalidating() + public void ActionDescriptors_UpdatesAndResubscripes_WhenChangeTokenTriggers() { // Arrange var actionDescriptorProvider = new Mock(); @@ -143,34 +94,61 @@ namespace Microsoft.AspNetCore.Mvc.Internal invocations++; }); var changeProvider = new TestChangeProvider(); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider.Object }, new[] { changeProvider }); // Act - 1 + var changeToken1 = actionDescriptorCollectionProvider.GetChangeToken(); var collection1 = actionDescriptorCollectionProvider.ActionDescriptors; + ActionDescriptorCollection captured = null; + changeToken1.RegisterChangeCallback((_) => + { + captured = actionDescriptorCollectionProvider.ActionDescriptors; + }, null); + // Assert - 1 + Assert.False(changeToken1.HasChanged); Assert.Equal(0, collection1.Version); Assert.Collection(collection1.Items, item => Assert.Same(expected1, item)); // Act - 2 changeProvider.TokenSource.Cancel(); + var changeToken2 = actionDescriptorCollectionProvider.GetChangeToken(); var collection2 = actionDescriptorCollectionProvider.ActionDescriptors; + changeToken2.RegisterChangeCallback((_) => + { + captured = actionDescriptorCollectionProvider.ActionDescriptors; + }, null); + // Assert - 2 + Assert.NotSame(changeToken1, changeToken2); + Assert.True(changeToken1.HasChanged); + Assert.False(changeToken2.HasChanged); + Assert.NotSame(collection1, collection2); + Assert.NotNull(captured); + Assert.Same(captured, collection2); Assert.Equal(1, collection2.Version); Assert.Collection(collection2.Items, item => Assert.Same(expected2, item)); // Act - 3 changeProvider.TokenSource.Cancel(); + var changeToken3 = actionDescriptorCollectionProvider.GetChangeToken(); var collection3 = actionDescriptorCollectionProvider.ActionDescriptors; // Assert - 3 + Assert.NotSame(changeToken2, changeToken3); + Assert.True(changeToken2.HasChanged); + Assert.False(changeToken3.HasChanged); + Assert.NotSame(collection2, collection3); + Assert.NotNull(captured); + Assert.Same(captured, collection3); Assert.Equal(2, collection3.Version); Assert.Collection(collection3.Items, item => Assert.Same(expected3, item)); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs index 0187f81666..c4374a5bbb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static ActionConstraintCache CreateCache(params IActionConstraintProvider[] providers) { - var descriptorProvider = new ActionDescriptorCollectionProvider( + var descriptorProvider = new DefaultActionDescriptorCollectionProvider( Enumerable.Empty(), Enumerable.Empty()); return new ActionConstraintCache(descriptorProvider, providers); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs index 5835a579f0..5b3adffcbc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs @@ -932,7 +932,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure private ControllerActionDescriptor InvokeActionSelector(RouteContext context) { var actionDescriptorProvider = GetActionDescriptorProvider(); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionDescriptorProvider }, Enumerable.Empty()); @@ -1092,7 +1092,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure private static ActionConstraintCache GetActionConstraintCache(IActionConstraintProvider[] actionConstraintProviders = null) { - var descriptorProvider = new ActionDescriptorCollectionProvider( + var descriptorProvider = new DefaultActionDescriptorCollectionProvider( Enumerable.Empty(), Enumerable.Empty()); return new ActionConstraintCache(descriptorProvider, actionConstraintProviders.AsEnumerable() ?? new List()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 416e359291..be31acba0b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -133,47 +133,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.True(actionInvokerCalled); } - [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] - public void GetChangeToken_MultipleChangeTokenProviders_ComposedResult() - { - // Arrange - var featureCollection = new FeatureCollection(); - featureCollection.Set(new EndpointFeature - { - RouteValues = new RouteValueDictionary() - }); - - var httpContextMock = new Mock(); - httpContextMock.Setup(m => m.Features).Returns(featureCollection); - - var descriptorProviderMock = new Mock(); - descriptorProviderMock.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); - - var actionInvokerMock = new Mock(); - - var actionInvokerProviderMock = new Mock(); - actionInvokerProviderMock.Setup(m => m.CreateInvoker(It.IsAny())).Returns(actionInvokerMock.Object); - - var changeTokenMock = new Mock(); - - var changeProvider1Mock = new Mock(); - changeProvider1Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object); - var changeProvider2Mock = new Mock(); - changeProvider2Mock.Setup(m => m.GetChangeToken()).Returns(changeTokenMock.Object); - - var dataSource = CreateMvcEndpointDataSource( - descriptorProviderMock.Object, - new MvcEndpointInvokerFactory(actionInvokerProviderMock.Object), - new[] { changeProvider1Mock.Object, changeProvider2Mock.Object }); - - // Act - var changeToken = dataSource.GetChangeToken(); - - // Assert - var compositeChangeToken = Assert.IsType(changeToken); - Assert.Equal(2, compositeChangeToken.ChangeTokens.Count); - } - [Theory] [InlineData("{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" })] [InlineData("{controller}/{id?}", new string[] { })] @@ -287,11 +246,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionDescriptorCollectionProviderMock.VerifyGet(m => m.ActionDescriptors, Times.Once); } - [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] + [Fact] public void Endpoints_ChangeTokenTriggered_EndpointsRecreated() { // Arrange - var actionDescriptorCollectionProviderMock = new Mock(); + var actionDescriptorCollectionProviderMock = new Mock(); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) .Returns(new ActionDescriptorCollection(new[] @@ -300,19 +259,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal }, version: 0)); CancellationTokenSource cts = null; + actionDescriptorCollectionProviderMock + .Setup(m => m.GetChangeToken()) + .Returns(() => + { + cts = new CancellationTokenSource(); + var changeToken = new CancellationChangeToken(cts.Token); - var changeProviderMock = new Mock(); - changeProviderMock.Setup(m => m.GetChangeToken()).Returns(() => - { - cts = new CancellationTokenSource(); - var changeToken = new CancellationChangeToken(cts.Token); + return changeToken; + }); - return changeToken; - }); - var dataSource = CreateMvcEndpointDataSource( - actionDescriptorCollectionProviderMock.Object, - actionDescriptorChangeProviders: new[] { changeProviderMock.Object }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollectionProviderMock.Object); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( string.Empty, "{controller}/{action}", @@ -752,15 +710,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, - MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null, - IEnumerable actionDescriptorChangeProviders = null) + MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) { if (actionDescriptorCollectionProvider == null) { - var mockDescriptorProvider = new Mock(); - mockDescriptorProvider.Setup(m => m.ActionDescriptors).Returns(new ActionDescriptorCollection(new List(), 0)); - - actionDescriptorCollectionProvider = mockDescriptorProvider.Object; + actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( + Array.Empty(), + Array.Empty()); } var serviceProviderMock = new Mock(); @@ -769,7 +725,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - actionDescriptorChangeProviders ?? Array.Empty(), serviceProviderMock.Object); return dataSource; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 298786c121..5936d34ef6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -351,7 +351,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } }); - var actionDescriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var actionDescriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new IActionDescriptorProvider[] { actionDescriptorProvider.Object, }, Enumerable.Empty()); @@ -398,7 +398,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing private static ActionConstraintCache GetActionConstraintCache(IActionConstraintProvider[] actionConstraintProviders = null) { - var descriptorProvider = new ActionDescriptorCollectionProvider( + var descriptorProvider = new DefaultActionDescriptorCollectionProvider( Enumerable.Empty(), Enumerable.Empty()); return new ActionConstraintCache(descriptorProvider, actionConstraintProviders.AsEnumerable() ?? new List()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs index e6ee1638f6..1e1d1f8951 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing .Setup(p => p.OnProvidersExecuted(It.IsAny())) .Verifiable(); - var descriptorCollectionProvider = new ActionDescriptorCollectionProvider( + var descriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionProvider.Object }, Enumerable.Empty()); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 40e556ad72..d216ad4aff 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1087,7 +1087,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }); } - [Fact(Skip = "https://github.com/aspnet/Routing/issues/722")] + [Fact] public async Task ApiExplorer_Updates_WhenActionDescriptorCollectionIsUpdated() { // Act - 1 From 96b77c866349a6d31508ce375ab98345a59604f1 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 27 Aug 2018 17:35:38 -0700 Subject: [PATCH 195/316] Fix aspnet/Routing#721 --- .../Internal/MvcEndpointDataSource.cs | 27 ++++++++++++------- .../ApplicationModelTest.cs | 2 +- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 560016a4c4..2d3f5e4ba2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -174,7 +174,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, - suppressLinkGeneration: false); + suppressLinkGeneration: false, + suppressPathMatching: false); endpoints.Add(subEndpoint); } @@ -213,7 +214,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, - suppressLinkGeneration: false); + suppressLinkGeneration: false, + suppressPathMatching: false); endpoints.Add(endpoint); } } @@ -227,7 +229,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo, - suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration); + suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration, + suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching); endpoints.Add(endpoint); } } @@ -375,7 +378,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal object nonInlineDefaults, int order, object source, - bool suppressLinkGeneration) + bool suppressLinkGeneration, + bool suppressPathMatching) { RequestDelegate requestDelegate = (context) => { @@ -403,7 +407,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal routeName, new RouteValueDictionary(action.RouteValues), source, - suppressLinkGeneration); + suppressLinkGeneration, + suppressPathMatching); var endpoint = new RouteEndpoint( requestDelegate, @@ -420,12 +425,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal string routeName, RouteValueDictionary requiredValues, object source, - bool suppressLinkGeneration) + bool suppressLinkGeneration, + bool suppressPathMatching) { var metadata = new List { - // REVIEW: Used for debugging. Consider removing before release - source, action }; @@ -475,6 +479,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.Add(new SuppressLinkGenerationMetadata()); } + if (suppressPathMatching) + { + metadata.Add(new SuppressMatchingMetadata()); + } + var metadataCollection = new EndpointMetadataCollection(metadata); return metadataCollection; } @@ -500,7 +509,5 @@ namespace Microsoft.AspNetCore.Mvc.Internal defaults[kvp.Key] = kvp.Value; } } - - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs index dcb50e1d8e..a698af7ba4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("From Header - HelloWorld", body); } - [Fact(Skip = "https://github.com/aspnet/Routing/issues/721")] + [Fact] public async Task ActionModelSuppressedForPathMatching_CannotBeRouted() { // Arrange & Act From 17d72c2b94679ca9da9f257200b61e201a5ec42e Mon Sep 17 00:00:00 2001 From: Massimiliano Donini Date: Fri, 24 Aug 2018 09:09:27 +0200 Subject: [PATCH 196/316] Fix msbuild targets to correctly copy deps.json --- .../netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets b/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets index 6cd039b00b..452fc9909d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets +++ b/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets @@ -53,7 +53,7 @@ Include="$([System.IO.Path]::ChangeExtension('%(_ContentRootProjectReferences.ResolvedFrom)', '.deps.json'))" /> - + \ No newline at end of file From 667ad4daff242d4a09f4ca68836e8aa3e10b5a02 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 23 Aug 2018 10:37:58 -0700 Subject: [PATCH 197/316] Use ClientErrorData to configure ClientErrorResultFilter Fixes #8289 --- .../ApiBehaviorOptions.cs | 38 +++++---- .../ClientErrorData.cs | 29 +++++++ .../MvcCoreServiceCollectionExtensions.cs | 1 + .../Infrastructure/ClientErrorResultFilter.cs | 26 +++--- .../Infrastructure/IClientErrorFactory.cs | 20 +++++ .../ProblemDetailsClientErrorFactory.cs | 44 ++++++++++ .../ApiBehaviorApplicationModelProvider.cs | 5 +- .../Internal/ApiBehaviorOptionsSetup.cs | 80 +++++++------------ .../Internal/MvcCoreLoggerExtensions.cs | 10 +-- .../ProblemDetails.cs | 4 +- .../ClientErrorResultFilterTest.cs | 42 +++------- .../ProblemDetalsClientErrorFactoryTest.cs | 65 +++++++++++++++ ...ApiBehaviorApplicationModelProviderTest.cs | 9 ++- .../Internal/ApiBehaviorOptionsSetupTest.cs | 4 +- .../ApiBehaviorTest.cs | 4 + .../CompatibilitySwitchIntegrationTest.cs | 8 +- 16 files changed, 265 insertions(+), 124 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs index 54614a2465..b2b2c2cd6f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc /// public class ApiBehaviorOptions : IEnumerable { - private readonly CompatibilitySwitch _suppressUseClientErrorFactory; + private readonly CompatibilitySwitch _suppressMapClientErrors; private readonly CompatibilitySwitch _suppressUseValidationProblemDetailsForInvalidModelStateResponses; private readonly ICompatibilitySwitch[] _switches; @@ -26,11 +26,11 @@ namespace Microsoft.AspNetCore.Mvc /// public ApiBehaviorOptions() { - _suppressUseClientErrorFactory = new CompatibilitySwitch(nameof(SuppressUseClientErrorFactory)); + _suppressMapClientErrors = new CompatibilitySwitch(nameof(SuppressMapClientErrors)); _suppressUseValidationProblemDetailsForInvalidModelStateResponses = new CompatibilitySwitch(nameof(SuppressUseValidationProblemDetailsForInvalidModelStateResponses)); _switches = new[] { - _suppressUseClientErrorFactory, + _suppressMapClientErrors, _suppressUseValidationProblemDetailsForInvalidModelStateResponses, }; } @@ -71,12 +71,16 @@ namespace Microsoft.AspNetCore.Mvc public bool SuppressConsumesConstraintForFormFileParameters { get; set; } /// - /// Gets or sets a value that determines if controllers with use - /// to transform certain certain client errors. + /// Gets or sets a value that determines if controllers with + /// transform certain certain client errors. /// - /// When false, is used to transform to the value - /// specified by the factory. In the default case, this converts instances to an - /// with . + /// When false, a result filter is added to API controller actions that transforms . + /// By default, is used to map to a + /// instance (returned as the value for ). + /// + /// + /// To customize the output of the filter (for e.g. to return a different error type), register a custom + /// implementation of of in the service collection. /// /// /// @@ -102,11 +106,11 @@ namespace Microsoft.AspNetCore.Mvc /// higher then this setting will have the value unless explicitly configured. /// /// - public bool SuppressUseClientErrorFactory + public bool SuppressMapClientErrors { // Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property. - get => _suppressUseClientErrorFactory.Value; - set => _suppressUseClientErrorFactory.Value = value; + get => _suppressMapClientErrors.Value; + set => _suppressMapClientErrors.Value = value; } /// @@ -148,11 +152,15 @@ namespace Microsoft.AspNetCore.Mvc } /// - /// Gets a map of HTTP status codes to factories. - /// Configured factories are used when is . + /// Gets a map of HTTP status codes to . Configured values + /// are used to transform to an + /// instance where the is . + /// + /// Use of this feature can be disabled by resetting . + /// /// - public IDictionary> ClientErrorFactory { get; } = - new Dictionary>(); + public IDictionary ClientErrorMapping { get; } = + new Dictionary(); IEnumerator IEnumerable.GetEnumerator() { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs b/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs new file mode 100644 index 0000000000..38b3448ece --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs @@ -0,0 +1,29 @@ +// 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 +{ + /// + /// Information for producing client errors. This type is used to configure client errors + /// produced by consumers of . + /// + public class ClientErrorData + { + /// + /// Gets or sets a link (URI) that describes the client error. + /// + /// + /// By default, this maps to . + /// + public string Link { get; set; } + + /// + /// Gets or sets the summary of the client error. + /// + /// + /// By default, this maps to and should not change + /// between multiple occurences of the same error. + /// + public string Title { get; set; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index 767aa0c33f..a0c082fdf6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -257,6 +257,7 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddSingleton, RedirectToRouteResultExecutor>(); services.TryAddSingleton, RedirectToPageResultExecutor>(); services.TryAddSingleton, ContentResultExecutor>(); + services.TryAddSingleton(); // // Route Handlers diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs index a6482dfe09..a212c50ea4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.Logging; @@ -11,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter { - private readonly IDictionary> _clientErrorFactory; + private readonly IClientErrorFactory _clientErrorFactory; private readonly ILogger _logger; /// @@ -20,10 +19,10 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure public int Order => -2000; public ClientErrorResultFilter( - ApiBehaviorOptions apiBehaviorOptions, + IClientErrorFactory clientErrorFactory, ILogger logger) { - _clientErrorFactory = apiBehaviorOptions?.ClientErrorFactory ?? throw new ArgumentNullException(nameof(apiBehaviorOptions)); + _clientErrorFactory = clientErrorFactory ?? throw new ArgumentNullException(nameof(clientErrorFactory)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -38,16 +37,19 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure throw new ArgumentNullException(nameof(context)); } - if (context.Result is IClientErrorActionResult clientErrorActionResult && - clientErrorActionResult.StatusCode is int statusCode && - _clientErrorFactory.TryGetValue(statusCode, out var factory)) + if (!(context.Result is IClientErrorActionResult clientError)) { - var result = factory(context); - - _logger.TransformingClientError(context.Result.GetType(), result?.GetType(), statusCode); - - context.Result = factory(context); + return; } + + var result = _clientErrorFactory.GetClientError(context, clientError); + if (result == null) + { + return; + } + + _logger.TransformingClientError(context.Result.GetType(), result?.GetType(), clientError.StatusCode); + context.Result = result; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs new file mode 100644 index 0000000000..b592c52a9b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs @@ -0,0 +1,20 @@ +// 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.Infrastructure +{ + /// + /// A factory for producing client errors. This contract is used by controllers annotated + /// with to transform . + /// + public interface IClientErrorFactory + { + /// + /// Transforms for the specified . + /// + /// The . + /// The . + /// THe that would be returned to the client. + IActionResult GetClientError(ActionContext actionContext, IClientErrorActionResult clientError); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs new file mode 100644 index 0000000000..1bf1e3ac43 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs @@ -0,0 +1,44 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal class ProblemDetailsClientErrorFactory : IClientErrorFactory + { + private readonly ApiBehaviorOptions _options; + + public ProblemDetailsClientErrorFactory(IOptions options) + { + _options = options?.Value ?? throw new ArgumentNullException(nameof(options)); + } + + public IActionResult GetClientError(ActionContext actionContext, IClientErrorActionResult clientError) + { + var problemDetails = new ProblemDetails + { + Status = clientError.StatusCode, + Type = "about:blank", + }; + + if (clientError.StatusCode is int statusCode && + _options.ClientErrorMapping.TryGetValue(statusCode, out var errorData)) + { + problemDetails.Title = errorData.Title; + problemDetails.Type = errorData.Link; + } + + return new ObjectResult(problemDetails) + { + StatusCode = problemDetails.Status, + ContentTypes = + { + "application/problem+json", + "application/problem+xml", + }, + }; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index bc1ac18c79..5b21f55378 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public ApiBehaviorApplicationModelProvider( IOptions apiBehaviorOptions, IModelMetadataProvider modelMetadataProvider, + IClientErrorFactory clientErrorFactory, ILoggerFactory loggerFactory) { _apiBehaviorOptions = apiBehaviorOptions.Value; @@ -45,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal loggerFactory.CreateLogger()); _clientErrorResultFilter = new ClientErrorResultFilter( - _apiBehaviorOptions, + clientErrorFactory, loggerFactory.CreateLogger()); } @@ -158,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private void AddClientErrorFilter(ActionModel actionModel) { - if (_apiBehaviorOptions.SuppressUseClientErrorFactory) + if (_apiBehaviorOptions.SuppressMapClientErrors) { return; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs index 15c4ef8892..4d64d9bb84 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (Version < CompatibilityVersion.Version_2_2) { - dictionary[nameof(ApiBehaviorOptions.SuppressUseClientErrorFactory)] = true; + dictionary[nameof(ApiBehaviorOptions.SuppressMapClientErrors)] = true; dictionary[nameof(ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses)] = true; } @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } options.InvalidModelStateResponseFactory = DefaultFactory; - ConfigureClientErrorFactories(options); + ConfigureClientErrorMapping(options); } public override void PostConfigure(string name, ApiBehaviorOptions options) @@ -57,9 +57,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal base.PostConfigure(name, options); // We want to use problem details factory only if - // (a) it has not been opted out of (SuppressUseClientErrorFactory = true) + // (a) it has not been opted out of (SuppressMapClientErrors = true) // (b) a different factory was configured - if (!options.SuppressUseClientErrorFactory && + if (!options.SuppressMapClientErrors && object.ReferenceEquals(options.InvalidModelStateResponseFactory, DefaultFactory)) { options.InvalidModelStateResponseFactory = ProblemDetailsFactory; @@ -67,77 +67,55 @@ namespace Microsoft.AspNetCore.Mvc.Internal } // Internal for unit testing - internal static void ConfigureClientErrorFactories(ApiBehaviorOptions options) + internal static void ConfigureClientErrorMapping(ApiBehaviorOptions options) { - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[400] = new ClientErrorData { - Status = 400, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.1", Title = Resources.ApiConventions_Title_400, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[401] = new ClientErrorData { - Status = 401, - Type = "https://tools.ietf.org/html/rfc7235#section-3.1", + Link = "https://tools.ietf.org/html/rfc7235#section-3.1", Title = Resources.ApiConventions_Title_401, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[403] = new ClientErrorData { - Status = 403, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.3", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.3", Title = Resources.ApiConventions_Title_403, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[404] = new ClientErrorData { - Status = 404, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.4", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.4", Title = Resources.ApiConventions_Title_404, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[406] = new ClientErrorData { - Status = 406, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.6", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.6", Title = Resources.ApiConventions_Title_406, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[409] = new ClientErrorData { - Status = 409, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.8", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.8", Title = Resources.ApiConventions_Title_409, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[415] = new ClientErrorData { - Status = 415, - Type = "https://tools.ietf.org/html/rfc7231#section-6.5.13", + Link = "https://tools.ietf.org/html/rfc7231#section-6.5.13", Title = Resources.ApiConventions_Title_415, - }); + }; - AddClientErrorFactory(new ProblemDetails + options.ClientErrorMapping[422] = new ClientErrorData { - Status = 422, - Type = "https://tools.ietf.org/html/rfc4918#section-11.2", + Link = "https://tools.ietf.org/html/rfc4918#section-11.2", Title = Resources.ApiConventions_Title_422, - }); - - void AddClientErrorFactory(ProblemDetails problemDetails) - { - var statusCode = problemDetails.Status.Value; - options.ClientErrorFactory[statusCode] = _ => new ObjectResult(problemDetails) - { - StatusCode = statusCode, - ContentTypes = - { - "application/problem+json", - "application/problem+xml", - }, - }; - } + }; } private static IActionResult DefaultInvalidModelStateResponse(ActionContext context) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 4c2d80972e..b4a223b8b2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static readonly Action _notMostEffectiveFilter; private static readonly Action, Exception> _registeredOutputFormatters; - private static readonly Action _transformingClientError; + private static readonly Action _transformingClientError; static MvcCoreLoggerExtensions() { @@ -651,10 +651,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal 48, "Skipped binding parameter '{ParameterName}' since its binding information disallowed it for the current request."); - _transformingClientError = LoggerMessage.Define( + _transformingClientError = LoggerMessage.Define( LogLevel.Trace, new EventId(49, nameof(Infrastructure.ClientErrorResultFilter)), - "Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType} produced from ClientErrorFactory'."); + "Replacing {InitialActionResultType} with status code {StatusCode} with {ReplacedActionResultType}."); } public static void RegisteredOutputFormatters(this ILogger logger, IEnumerable outputFormatters) @@ -1585,9 +1585,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int statusCode) + public static void TransformingClientError(this ILogger logger, Type initialType, Type replacedType, int? statusCode) { - _transformingClientError(logger, initialType, replacedType, statusCode, null); + _transformingClientError(logger, initialType, statusCode, replacedType, null); } private static void LogFilterExecutionPlan( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs index 35ac215562..573419b0bc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc public string Type { get; set; } /// - /// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence + /// A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence /// of the problem, except for purposes of localization(e.g., using proactive content negotiation; /// see[RFC7231], Section 3.4). /// @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc public string Detail { get; set; } /// - /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced. + /// A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced. /// public string Instance { get; set; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs index 7c3f55858d..83098744ff 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs @@ -32,35 +32,25 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure } [Fact] - public void OnResultExecuting_DoesNothing_IfStatusCodeDoesNotExistInApiBehaviorOptions() + public void OnResultExecuting_DoesNothing_IfTransformedValueIsNull() { // Arrange var actionResult = new NotFoundResult(); var context = GetContext(actionResult); - var filter = GetFilter(new ApiBehaviorOptions()); - - // Act - filter.OnResultExecuting(context); - - // Assert - Assert.Same(actionResult, context.Result); - } - - [Fact] - public void OnResultExecuting_DoesNothing_IfResultDoesNotHaveStatusCode() - { - // Arrange - var actionResult = new Mock() - .As() - .Object; - var context = GetContext(actionResult); - var filter = GetFilter(new ApiBehaviorOptions()); + var factory = new Mock(); + factory + .Setup(f => f.GetClientError(It.IsAny(), It.IsAny())) + .Returns((IActionResult)null) + .Verifiable(); + + var filter = new ClientErrorResultFilter(factory.Object, NullLogger.Instance); // Act filter.OnResultExecuting(context); // Assert Assert.Same(actionResult, context.Result); + factory.Verify(); } [Fact] @@ -78,18 +68,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure Assert.Same(Result, context.Result); } - private static ClientErrorResultFilter GetFilter(ApiBehaviorOptions options = null) + private static ClientErrorResultFilter GetFilter() { - var apiBehaviorOptions = options ?? GetOptions(); - var filter = new ClientErrorResultFilter(apiBehaviorOptions, NullLogger.Instance); - return filter; - } + var factory = Mock.Of( + f => f.GetClientError(It.IsAny(), It.IsAny()) == Result); - private static ApiBehaviorOptions GetOptions() - { - var apiBehaviorOptions = new ApiBehaviorOptions(); - apiBehaviorOptions.ClientErrorFactory[404] = _ => Result; - return apiBehaviorOptions; + return new ClientErrorResultFilter(factory, NullLogger.Instance); } private static ResultExecutingContext GetContext(IActionResult actionResult) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs new file mode 100644 index 0000000000..65a53a8158 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + public class ProblemDetalsClientErrorFactoryTest + { + [Fact] + public void GetClientError_ReturnsProblemDetails_IfNoMappingWasFound() + { + // Arrange + var clientError = new UnsupportedMediaTypeResult(); + var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions + { + ClientErrorMapping = + { + [405] = new ClientErrorData { Link = "Some link", Title = "Summary" }, + }, + })); + + // Act + var result = factory.GetClientError(new ActionContext(), clientError); + + // Assert + var objectResult = Assert.IsType(result); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes); + var problemDetails = Assert.IsType(objectResult.Value); + Assert.Equal(415, problemDetails.Status); + Assert.Equal("about:blank", problemDetails.Type); + Assert.Null(problemDetails.Title); + Assert.Null(problemDetails.Detail); + Assert.Null(problemDetails.Instance); + } + + [Fact] + public void GetClientError_ReturnsProblemDetails() + { + // Arrange + var clientError = new UnsupportedMediaTypeResult(); + var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions + { + ClientErrorMapping = + { + [415] = new ClientErrorData { Link = "Some link", Title = "Summary" }, + }, + })); + + // Act + var result = factory.GetClientError(new ActionContext(), clientError); + + // Assert + var objectResult = Assert.IsType(result); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes); + var problemDetails = Assert.IsType(objectResult.Value); + Assert.Equal(415, problemDetails.Status); + Assert.Equal("Some link", problemDetails.Type); + Assert.Equal("Summary", problemDetails.Title); + Assert.Null(problemDetails.Detail); + Assert.Null(problemDetails.Instance); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index 3b5d4415de..c0c415573f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal @@ -1081,7 +1082,7 @@ Environment.NewLine + "int b"; var context = GetContext(typeof(TestApiController)); var options = new ApiBehaviorOptions { - SuppressUseClientErrorFactory = true, + SuppressMapClientErrors = true, InvalidModelStateResponseFactory = _ => null, }; var provider = GetProvider(options); @@ -1122,7 +1123,11 @@ Environment.NewLine + "int b"; var loggerFactory = NullLoggerFactory.Instance; modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); - return new ApiBehaviorApplicationModelProvider(optionsAccessor, modelMetadataProvider, loggerFactory); + return new ApiBehaviorApplicationModelProvider( + optionsAccessor, + modelMetadataProvider, + Mock.Of(), + loggerFactory); } private static ApplicationModelProviderContext GetContext( diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs index 66f4f7933b..622a36e5ff 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void Configure_AddsClientErrorFactories() + public void Configure_AddsClientErrorMappings() { // Arrange var expected = new[] { 400, 401, 403, 404, 406, 409, 415, 422, }; @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal optionsSetup.Configure(options); // Assert - Assert.Equal(expected, options.ClientErrorFactory.Keys); + Assert.Equal(expected, options.ClientErrorMapping.Keys); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index 0d0a85999f..d3ec4d9e29 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -97,6 +97,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert await response.AssertStatusCodeAsync(HttpStatusCode.UnsupportedMediaType); + var content = await response.Content.ReadAsStringAsync(); + var problemDetails = JsonConvert.DeserializeObject(content); + Assert.Equal((int)HttpStatusCode.UnsupportedMediaType, problemDetails.Status); + Assert.Equal("Unsupported Media Type", problemDetails.Title); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 813c148bfe..2413eb34bf 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -43,7 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(mvcOptions.EnableEndpointRouting); Assert.Null(mvcOptions.MaxValidationDepth); Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); - Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory); + Assert.True(apiBehaviorOptions.SuppressMapClientErrors); } [Fact] @@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(mvcOptions.EnableEndpointRouting); Assert.Null(mvcOptions.MaxValidationDepth); Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); - Assert.True(apiBehaviorOptions.SuppressUseClientErrorFactory); + Assert.True(apiBehaviorOptions.SuppressMapClientErrors); } [Fact] @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(mvcOptions.EnableEndpointRouting); Assert.Equal(32, mvcOptions.MaxValidationDepth); Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); - Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory); + Assert.False(apiBehaviorOptions.SuppressMapClientErrors); } [Fact] @@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(mvcOptions.EnableEndpointRouting); Assert.Equal(32, mvcOptions.MaxValidationDepth); Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); - Assert.False(apiBehaviorOptions.SuppressUseClientErrorFactory); + Assert.False(apiBehaviorOptions.SuppressMapClientErrors); } // This just does the minimum needed to be able to resolve these options. From d09c3c9e2889e9e1c41cdcf3b45f3d724f98fc9f Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 24 Aug 2018 11:12:28 -0700 Subject: [PATCH 198/316] Polish ProblemDetails * Add ability to set extended members on ProblemDetails * Skip empty valued properties when serializing ProblemDetails Fixes #8296 Fixes #8317 --- .../ProblemDetails.cs | 24 +- .../ProblemDetailsWrapper.cs | 187 +++++++++++++++ .../Properties/AssemblyInfo.cs | 6 + .../SerializableErrorWrapper.cs | 2 +- .../ValidationProblemDetailsWrapper.cs | 126 ++++++++++ .../WrapperProviderFactoriesExtensions.cs | 19 ++ .../WrapperProviderFactory.cs | 50 ++++ ...XmlDataContractSerializerInputFormatter.cs | 3 +- ...mlDataContractSerializerOutputFormatter.cs | 3 +- .../XmlSerializerInputFormatter.cs | 3 +- .../XmlSerializerOutputFormatter.cs | 3 +- .../ProblemDetailsWrapperTest.cs | 100 ++++++++ .../ValidationProblemDetailsWrapperTest.cs | 226 ++++++++++++++++++ .../WrapperProviderFactoryExtensionsTest.cs | 34 +++ .../WrapperProviderFactoryTest.cs | 63 +++++ .../ApiBehaviorTest.cs | 62 ++++- .../Infrastructure/HttpClientExtensions.cs | 4 +- ...ontractSerializerFormattersWrappingTest.cs | 65 ++++- .../XmlSerializerFormattersWrappingTest.cs | 63 +++++ .../Controllers/ContactApiController.cs | 35 +++ .../Controllers/XmlApiControllerBase.cs | 55 +++++ .../XmlDataContractApiController.cs | 29 +++ .../Controllers/XmlSerializedApiController.cs | 29 +++ 23 files changed, 1175 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs create mode 100644 test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs create mode 100644 test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs create mode 100644 test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs index 573419b0bc..44b816aa05 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Newtonsoft.Json; namespace Microsoft.AspNetCore.Mvc { @@ -17,28 +18,47 @@ namespace Microsoft.AspNetCore.Mvc /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be /// "about:blank". /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Type { get; set; } /// - /// A short, human-readable summary of the problem type. It SHOULD NOT change from occurrence to occurrence + /// A short, human-readable summary of the problem type.It SHOULD NOT change from occurrence to occurrence /// of the problem, except for purposes of localization(e.g., using proactive content negotiation; /// see[RFC7231], Section 3.4). /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Title { get; set; } /// /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public int? Status { get; set; } /// /// A human-readable explanation specific to this occurrence of the problem. /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Detail { get; set; } /// - /// A URI reference that identifies the specific occurrence of the problem. It may or may not yield further information if dereferenced. + /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced. /// + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public string Instance { get; set; } + + /// + /// Gets the for extension members. + /// + /// Problem type definitions MAY extend the problem details object with additional members. Extension members appear in the same namespace as + /// other members of a problem type. + /// + /// + /// + /// The round-tripping behavior for is determined by the implementation of the Input \ Output formatters. + /// In particular, complex types or collection types may not round-trip to the original type when using the built-in JSON or XML formatters. + /// + [JsonExtensionData] + public IDictionary Extensions { get; } = new Dictionary(StringComparer.Ordinal); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs new file mode 100644 index 0000000000..30775bd1b1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs @@ -0,0 +1,187 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Wrapper class for to enable it to be serialized by the xml formatters. + /// + [XmlRoot(nameof(ProblemDetails))] + public class ProblemDetailsWrapper : IXmlSerializable, IUnwrappable + { + /// + /// Key used to represent dictionary elements with empty keys + /// + protected static readonly string EmptyKey = SerializableErrorWrapper.EmptyKey; + + /// + /// Initializes a new instance of . + /// + public ProblemDetailsWrapper() + : this(new ProblemDetails()) + { + } + + /// + /// Initializes a new instance of . + /// + public ProblemDetailsWrapper(ProblemDetails problemDetails) + { + ProblemDetails = problemDetails; + } + + internal ProblemDetails ProblemDetails { get; } + + /// + public XmlSchema GetSchema() => null; + + /// + public virtual void ReadXml(XmlReader reader) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + reader.ReadStartElement(); + while (reader.NodeType != XmlNodeType.EndElement) + { + var key = XmlConvert.DecodeName(reader.LocalName); + ReadValue(reader, key); + + reader.MoveToContent(); + } + + reader.ReadEndElement(); + } + + /// + /// Reads the value for the specified from the . + /// + /// The . + /// The name of the node. + protected virtual void ReadValue(XmlReader reader, string name) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + var value = reader.ReadInnerXml(); + + switch (name) + { + case nameof(ProblemDetails.Detail): + ProblemDetails.Detail = value; + break; + + case nameof(ProblemDetails.Instance): + ProblemDetails.Instance = value; + break; + + case nameof(ProblemDetails.Status): + ProblemDetails.Status = string.IsNullOrEmpty(value) ? + (int?)null : + int.Parse(value, CultureInfo.InvariantCulture); + break; + + case nameof(ProblemDetails.Title): + ProblemDetails.Title = value; + break; + + case nameof(ProblemDetails.Type): + ProblemDetails.Type = value; + break; + + default: + if (string.Equals(name, EmptyKey, StringComparison.Ordinal)) + { + name = string.Empty; + } + + ProblemDetails.Extensions.Add(name, value); + break; + } + } + + /// + public virtual void WriteXml(XmlWriter writer) + { + if (!string.IsNullOrEmpty(ProblemDetails.Detail)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName(nameof(ProblemDetails.Detail)), + ProblemDetails.Detail); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Instance)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName(nameof(ProblemDetails.Instance)), + ProblemDetails.Instance); + } + + if (ProblemDetails.Status.HasValue) + { + writer.WriteStartElement(XmlConvert.EncodeLocalName(nameof(ProblemDetails.Status))); + writer.WriteValue(ProblemDetails.Status.Value); + writer.WriteEndElement(); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Title)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName(nameof(ProblemDetails.Title)), + ProblemDetails.Title); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Type)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName(nameof(ProblemDetails.Type)), + ProblemDetails.Type); + } + + foreach (var keyValuePair in ProblemDetails.Extensions) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + + if (string.IsNullOrEmpty(key)) + { + key = EmptyKey; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); + if (value != null) + { + writer.WriteValue(value); + } + + writer.WriteEndElement(); + } + } + + object IUnwrappable.Unwrap(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + return ProblemDetails; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..61797e230b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs @@ -0,0 +1,6 @@ +// 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.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Formatters.Xml.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs index e8a28f576f..8a72825a80 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Element name used when ModelStateEntry's Key is empty. Dash in element name should avoid collisions with // other ModelState entries because the character is not legal in an expression name. - private static readonly string EmptyKey = "MVC-Empty"; + internal static readonly string EmptyKey = "MVC-Empty"; // Note: XmlSerializer requires to have default constructor public SerializableErrorWrapper() diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs new file mode 100644 index 0000000000..b8787ee0e0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs @@ -0,0 +1,126 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Xml; +using System.Xml.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Wrapper class for to enable it to be serialized by the xml formatters. + /// + [XmlRoot(nameof(ValidationProblemDetails))] + public class ValidationProblemDetailsWrapper : ProblemDetailsWrapper, IUnwrappable + { + private static readonly string ErrorKey = "MVC-Errors"; + + /// + /// Initializes a new instance of . + /// + public ValidationProblemDetailsWrapper() + : this(new ValidationProblemDetails()) + { + } + + /// + /// Initializes a new instance of for the specified + /// . + /// + /// The . + public ValidationProblemDetailsWrapper(ValidationProblemDetails problemDetails) + : base(problemDetails) + { + ProblemDetails = problemDetails; + } + + internal new ValidationProblemDetails ProblemDetails { get; } + + /// + protected override void ReadValue(XmlReader reader, string name) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + if (string.Equals(name, ErrorKey, StringComparison.Ordinal)) + { + reader.Read(); + ReadErrorProperty(reader); + } + else + { + base.ReadValue(reader, name); + } + } + + private void ReadErrorProperty(XmlReader reader) + { + if (reader.IsEmptyElement) + { + return; + } + + while (reader.NodeType != XmlNodeType.EndElement) + { + var key = XmlConvert.DecodeName(reader.LocalName); + var value = reader.ReadInnerXml(); + if (string.Equals(EmptyKey, key, StringComparison.Ordinal)) + { + key = string.Empty; + } + + ProblemDetails.Errors.Add(key, new[] { value }); + reader.MoveToContent(); + } + } + + /// + public override void WriteXml(XmlWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + base.WriteXml(writer); + + if (ProblemDetails.Errors.Count == 0) + { + return; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(ErrorKey)); + + foreach (var keyValuePair in ProblemDetails.Errors) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + if (string.IsNullOrEmpty(key)) + { + key = EmptyKey; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); + if (value != null) + { + writer.WriteValue(value); + } + + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + object IUnwrappable.Unwrap(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + return ProblemDetails; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs index 1ccea1645d..6ff62a8ec0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs @@ -44,5 +44,24 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml return null; } + + internal static IList GetDefaultProviderFactories() + { + var wrapperProviderFactories = new List(); + + wrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + + wrapperProviderFactories.Add(new WrapperProviderFactory( + typeof(ProblemDetails), + typeof(ProblemDetailsWrapper), + value => new ProblemDetailsWrapper((ProblemDetails)value))); + + wrapperProviderFactories.Add(new WrapperProviderFactory( + typeof(ValidationProblemDetails), + typeof(ValidationProblemDetailsWrapper), + value => new ValidationProblemDetailsWrapper((ValidationProblemDetails)value))); + + return wrapperProviderFactories; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs new file mode 100644 index 0000000000..3f7c4a48af --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs @@ -0,0 +1,50 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + internal class WrapperProviderFactory : IWrapperProviderFactory + { + public WrapperProviderFactory(Type declaredType, Type wrappingType, Func wrapper) + { + DeclaredType = declaredType; + WrappingType = wrappingType; + Wrapper = wrapper; + } + + public Type DeclaredType { get; } + + public Type WrappingType { get; } + + public Func Wrapper { get; } + + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context.DeclaredType == DeclaredType) + { + return new WrapperProvider(this); + } + + return null; + } + + private class WrapperProvider : IWrapperProvider + { + private readonly WrapperProviderFactory _wrapperFactory; + + public WrapperProvider(WrapperProviderFactory wrapperFactory) + { + _wrapperFactory = wrapperFactory; + } + + public Type WrappingType => _wrapperFactory.WrappingType; + + public object Wrap(object original) + { + return _wrapperFactory.Wrapper(original); + } + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs index 7916715002..b5d6d74a22 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs @@ -46,8 +46,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters _serializerSettings = new DataContractSerializerSettings(); - WrapperProviderFactories = new List(); - WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs index a0f374cc27..6a312d903d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -76,9 +76,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters _serializerSettings = new DataContractSerializerSettings(); - WrapperProviderFactories = new List(); + WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); - WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); _logger = loggerFactory?.CreateLogger(GetType()); } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs index 6708c81257..2c1ea40bbd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs @@ -43,8 +43,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); - WrapperProviderFactories = new List(); - WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); + WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs index c71be18264..e24b2c9d20 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -73,9 +73,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters WriterSettings = writerSettings; - WrapperProviderFactories = new List(); + WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); - WrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); _logger = loggerFactory?.CreateLogger(GetType()); } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs new file mode 100644 index 0000000000..b6760f0f15 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs @@ -0,0 +1,100 @@ +// 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 System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class ProblemDetailsWrapperTest + { + [Fact] + public void ReadXml_ReadsProblemDetailsXml() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "403" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + "Test Value 3" + + ""; + var serializer = new DataContractSerializer(typeof(ProblemDetailsWrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(403, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal("Test Value 3", kvp.Value); + }, + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }); + } + + + [Fact] + public void WriteXml_WritesValidXml() + { + // Arrange + var problemDetails = new ProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2", + [""] = "Test Value 3", + }, + }; + + var wrapper = new ProblemDetailsWrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + "Test Value 3" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs new file mode 100644 index 0000000000..50630a0082 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs @@ -0,0 +1,226 @@ +// 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 System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class ValidationProblemDetailsWrapperTest + { + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + "" + + "Test error 1 Test error 2" + + "<_x005B_error2_x005D_>Test error 3" + + "Test error 4" + + "" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(400, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }); + + Assert.Collection( + problemDetails.Errors.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal(new[] { "Test error 4" }, kvp.Value); + }, + kvp => + { + Assert.Equal("[error2]", kvp.Key); + Assert.Equal(new[] { "Test error 3" }, kvp.Value); + }, + kvp => + { + Assert.Equal("error1", kvp.Key); + Assert.Equal(new[] { "Test error 1 Test error 2" }, kvp.Value); + }); + } + + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml_WithNoErrors() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(400, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }, + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }); + + Assert.Empty(problemDetails.Errors); + } + + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml_WithEmptyErrorsElement() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal(400, problemDetails.Status); + Assert.Empty(problemDetails.Errors); + } + + [Fact] + public void WriteXml_WritesValidXml() + { + // Arrange + var problemDetails = new ValidationProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2" + }, + Errors = + { + { "error1", new[] {"Test error 1", "Test error 2" } }, + { "[error2]", new[] {"Test error 3" } }, + { "", new[] { "Test error 4" } }, + } + }; + + var wrapper = new ValidationProblemDetailsWrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + "" + + "Test error 1 Test error 2" + + "<_x005B_error2_x005D_>Test error 3" + + "Test error 4" + + "" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + + [Fact] + public void WriteXml_WithNoValidationErrors() + { + // Arrange + var problemDetails = new ValidationProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2" + }, + }; + + var wrapper = new ValidationProblemDetailsWrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs new file mode 100644 index 0000000000..ca981f679a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs @@ -0,0 +1,34 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class WrapperProviderFactoryExtensionsTest + { + [Fact] + public void GetDefaultProviderFactories_GetsFactoriesUsedByInputAndOutputFormatters() + { + // Act + var factoryProviders = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); + + // Assert + Assert.Collection( + factoryProviders, + factory => Assert.IsType(factory), + factory => + { + var wrapperProviderFactory = Assert.IsType(factory); + Assert.Equal(typeof(ProblemDetails), wrapperProviderFactory.DeclaredType); + Assert.Equal(typeof(ProblemDetailsWrapper), wrapperProviderFactory.WrappingType); + }, + factory => + { + var wrapperProviderFactory = Assert.IsType(factory); + Assert.Equal(typeof(ValidationProblemDetails), wrapperProviderFactory.DeclaredType); + Assert.Equal(typeof(ValidationProblemDetailsWrapper), wrapperProviderFactory.WrappingType); + }); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs new file mode 100644 index 0000000000..c067ed4ab9 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs @@ -0,0 +1,63 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class WrapperProviderFactoryTest + { + [Fact] + public void GetProvider_ReturnsNull_IfTypeDoesNotMatch() + { + // Arrange + var provider = new WrapperProviderFactory( + typeof(ProblemDetails), + typeof(ProblemDetailsWrapper), + _ => null); + var context = new WrapperProviderContext(typeof(SerializableError), isSerialization: true); + + // Act + var result = provider.GetProvider(context); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetProvider_ReturnsNull_IfTypeIsSubtype() + { + // Arrange + var provider = new WrapperProviderFactory( + typeof(ProblemDetails), + typeof(ProblemDetailsWrapper), + _ => null); + var context = new WrapperProviderContext(typeof(ValidationProblemDetails), isSerialization: true); + + // Act + var result = provider.GetProvider(context); + + // Assert + Assert.Null(result); + } + + [Fact] + public void GetProvider_ReturnsValue_IfTypeMatches() + { + // Arrange + var expected = new object(); + var providerFactory = new WrapperProviderFactory( + typeof(ProblemDetails), + typeof(ProblemDetailsWrapper), + _ => expected); + var context = new WrapperProviderContext(typeof(ProblemDetails), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + var result = provider.Wrap(new ProblemDetails()); + + // Assert + Assert.Same(expected, result); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index d3ec4d9e29..127d612fdd 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using BasicWebSite.Models; using Microsoft.AspNetCore.Hosting; using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -116,8 +117,8 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }; var expected = new Dictionary { - {"Name", new string[] {"The field Name must be a string with a minimum length of 5 and a maximum length of 30."}}, - {"Zip", new string[]{ @"The field Zip must match the regular expression '\d{5}'."}} + {"Name", new[] {"The field Name must be a string with a minimum length of 5 and a maximum length of 30."}}, + {"Zip", new[] { @"The field Zip must match the regular expression '\d{5}'."}} }; var contactString = JsonConvert.SerializeObject(contactModel); @@ -261,5 +262,62 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var problemDetails = JsonConvert.DeserializeObject(content); Assert.Equal(404, problemDetails.Status); } + + [Fact] + public async Task SerializingProblemDetails_IgnoresNullValuedProperties() + { + // Arrange + var expected = new[] { "status", "title", "type" }; + + // Act + var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + + // Verify that null-valued properties on ProblemDetails are not serialized. + var json = JObject.Parse(content); + Assert.Equal(expected, json.Properties().OrderBy(p => p.Name).Select(p => p.Name)); + } + + [Fact] + public async Task SerializingProblemDetails_WithAllValuesSpecified() + { + // Arrange + var expected = new[] { "detail", "instance", "status", "title", "tracking-id", "type" }; + + // Act + var response = await Client.GetAsync("/contact/ActionReturningProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + var json = JObject.Parse(content); + Assert.Equal(expected, json.Properties().OrderBy(p => p.Name).Select(p => p.Name)); + } + + [Fact] + public async Task SerializingValidationProblemDetails_WithExtensionData() + { + // Act + var response = await Client.GetAsync("/contact/ActionReturningValidationProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + var validationProblemDetails = JsonConvert.DeserializeObject(content); + + Assert.Equal("Error", validationProblemDetails.Title); + Assert.Equal(400, validationProblemDetails.Status); + Assert.Equal("27", validationProblemDetails.Extensions["tracking-id"]); + Assert.Collection( + validationProblemDetails.Errors, + kvp => + { + Assert.Equal("Error1", kvp.Key); + Assert.Equal(new[] { "Error Message" }, kvp.Value); + }); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs index 94e52c7e14..184e83cd31 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests throw new StatusCodeMismatchException { - ExpectedStatusCode = HttpStatusCode.OK, + ExpectedStatusCode = expectedStatusCode, ActualStatusCode = response.StatusCode, ResponseContent = responseContent, }; @@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { get { - return $"Excepted status code 200. Actual {ActualStatusCode}. Response Content:" + Environment.NewLine + ResponseContent; + return $"Excepted status code {ExpectedStatusCode}. Actual {ActualStatusCode}. Response Content:" + Environment.NewLine + ResponseContent; } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs index 5e6443a862..9ee16921a6 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs @@ -208,5 +208,68 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "", result); } + + [Fact] + public async Task ProblemDetails_IsSerialized() + { + // Arrange + var expected = @"404Not Foundhttps://tools.ietf.org/html/rfc7231#section-6.5.4"; + + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningClientErrorStatusCodeResult"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ProblemDetails_WithExtensionMembers_IsSerialized() + { + // Arrange + var expected = @"instance404title +correlationAccount1 Account2"; + + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ValidationProblemDetails_IsSerialized() + { + // Arrange + var expected = @"400One or more validation errors occurred. +The State field is required."; + + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationProblem"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ValidationProblemDetails_WithExtensionMembers_IsSerialized() + { + // Arrange + var expected = @"some detail400One or more validation errors occurred. +some typecorrelationErrorValue"; + + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationDetailsWithMetadata"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } -} \ No newline at end of file +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs index 2b8ddd9a06..d54cd3880c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs @@ -183,5 +183,68 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "key2-error", result); } + + [Fact] + public async Task ProblemDetails_IsSerialized() + { + // Arrange + var expected = @"404Not Foundhttps://tools.ietf.org/html/rfc7231#section-6.5.4"; + + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningClientErrorStatusCodeResult"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ProblemDetails_WithExtensionMembers_IsSerialized() + { + // Arrange + var expected = @"instance404title +correlationAccount1 Account2"; + + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ValidationProblemDetails_IsSerialized() + { + // Arrange + var expected = @"400One or more validation errors occurred. +The State field is required."; + + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationProblem"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + + [Fact] + public async Task ValidationProblemDetails_WithExtensionMembers_IsSerialized() + { + // Arrange + var expected = @"some detail400One or more validation errors occurred. +some typecorrelationErrorValue"; + + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationDetailsWithMetadata"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } } diff --git a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs index a2cd22afc5..c2d3400e1f 100644 --- a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs +++ b/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs @@ -91,6 +91,41 @@ namespace BasicWebSite return NotFound(); } + [HttpGet("[action]")] + public ActionResult ActionReturningProblemDetails() + { + return NotFound(new ProblemDetails + { + Title = "Not Found", + Type = "Type", + Detail = "Detail", + Status = 404, + Instance = "Instance", + Extensions = + { + ["tracking-id"] = 27, + }, + }); + } + + [HttpGet("[action]")] + public ActionResult ActionReturningValidationProblemDetails() + { + return BadRequest(new ValidationProblemDetails + { + Title = "Error", + Status = 400, + Extensions = + { + ["tracking-id"] = "27", + }, + Errors = + { + { "Error1", new[] { "Error Message" } }, + }, + }); + } + private class TestModelBinder : IModelBinder { public Task BindModelAsync(ModelBindingContext bindingContext) diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs b/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs new file mode 100644 index 0000000000..5296428144 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc; +using XmlFormattersWebSite.Models; + +namespace XmlFormattersWebSite +{ + [ApiController] + [Route("api/[controller]/[action]")] + public abstract class XmlApiControllerBase : ControllerBase + { + [HttpGet] + public ActionResult ActionReturningClientErrorStatusCodeResult() + => NotFound(); + + [HttpGet] + public ActionResult ActionReturningProblemDetails() + { + return NotFound(new ProblemDetails + { + Instance = "instance", + Title = "title", + Extensions = + { + ["Correlation"] = "correlation", + ["Accounts"] = new[] { "Account1", "Account2" }, + }, + }); + } + + [HttpGet] + public ActionResult ActionReturningValidationProblem([FromQuery] Address address) + => throw new NotImplementedException(); + + [HttpGet] + public ActionResult ActionReturningValidationDetailsWithMetadata() + { + return new BadRequestObjectResult(new ValidationProblemDetails + { + Detail = "some detail", + Type = "some type", + Extensions = + { + ["CorrelationId"] = "correlation", + }, + Errors = + { + ["Error1"] = new[] { "ErrorValue"}, + }, + }); + } + } +} diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs new file mode 100644 index 0000000000..dd8f228caa --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs @@ -0,0 +1,29 @@ +// 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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace XmlFormattersWebSite +{ + [SetupOutputFormatters] + public class XmlDataContractApiController : XmlApiControllerBase + { + private class SetupOutputFormattersAttribute : ResultFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + if (!(context.Result is ObjectResult objectResult)) + { + return; + } + + // Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter + // selection. The globally configured formatters rely on custom content-type to perform conneg which does not play + // well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller. + objectResult.Formatters.Add(new XmlDataContractSerializerOutputFormatter()); + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs new file mode 100644 index 0000000000..6ee3ec4708 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs @@ -0,0 +1,29 @@ +// 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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Formatters; + +namespace XmlFormattersWebSite +{ + [SetupOutputFormatters] + public class XmlSerializerApiController : XmlApiControllerBase + { + private class SetupOutputFormattersAttribute : ResultFilterAttribute + { + public override void OnResultExecuting(ResultExecutingContext context) + { + if (!(context.Result is ObjectResult objectResult)) + { + return; + } + + // Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter + // selection. The globally configured formatters rely on custom content-type to perform conneg which does not play + // well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller. + objectResult.Formatters.Add(new XmlSerializerOutputFormatter()); + } + } + } +} \ No newline at end of file From 234b003b313c4f637d46c356a2badbd92d678a10 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Tue, 28 Aug 2018 10:39:00 -0700 Subject: [PATCH 199/316] Set longRunningTestSeconds for Functional tests --- .../Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json index 1c72a421ad..0d8a1f6a45 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json @@ -1,3 +1,4 @@ { - "shadowCopy": false + "shadowCopy": false, + "longRunningTestSeconds": 60 } From 28f96bf83293d2947bc5e6d4244dbae7ea496433 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 Aug 2018 08:16:57 +1200 Subject: [PATCH 200/316] Fix obsolete build warning (#8358) --- .../Internal/AttributeRoutingTest.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs index c9d281b80d..4c475cadad 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs @@ -175,8 +175,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal .SetupGet(o => o.Value) .Returns(new RouteOptions()); +#pragma warning disable CS0618 // Type or member is obsolete + var inlineConstraintResolver = new DefaultInlineConstraintResolver(routeOptions.Object); +#pragma warning restore CS0618 // Type or member is obsolete + var services = new ServiceCollection() - .AddSingleton(new DefaultInlineConstraintResolver(routeOptions.Object)) + .AddSingleton(inlineConstraintResolver) .AddSingleton(); services.AddSingleton(); From b649133eec344e733d1724964a3255b972ed5ae5 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 Aug 2018 08:57:53 +1200 Subject: [PATCH 201/316] Refactor KnownRouteValueConstraint to not require HttpContext (#8352) --- build/dependencies.props | 4 +- .../Routing/KnownRouteValueConstraint.cs | 82 ++++++++----- .../Internal/MvcEndpointDataSourceTests.cs | 32 +++-- .../Routing/KnownRouteValueConstraintTests.cs | 109 ++++++++++++++++-- 4 files changed, 172 insertions(+), 55 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index b0c3540479..d718620eed 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview2-35090 2.2.0-preview2-35090 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 + 2.2.0-a-preview2-routeconstraint-httpcontext-16912 + 2.2.0-a-preview2-routeconstraint-httpcontext-16912 2.2.0-preview2-35090 2.2.0-preview2-35090 2.2.0-preview2-35090 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs index a80b3b510c..6989c22695 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs @@ -14,8 +14,26 @@ namespace Microsoft.AspNetCore.Mvc.Routing { public class KnownRouteValueConstraint : IRouteConstraint { + private readonly IActionDescriptorCollectionProvider _actionDescriptorCollectionProvider; private RouteValuesCollection _cachedValuesCollection; + [Obsolete("This constructor is obsolete. Use KnownRouteValueConstraint.ctor(IActionDescriptorCollectionProvider) instead.")] + public KnownRouteValueConstraint() + { + // Empty constructor for backwards compatibility + // Services will need to be resolved from HttpContext when this ctor is used + } + + public KnownRouteValueConstraint(IActionDescriptorCollectionProvider actionDescriptorCollectionProvider) + { + if (actionDescriptorCollectionProvider == null) + { + throw new ArgumentNullException(nameof(actionDescriptorCollectionProvider)); + } + + _actionDescriptorCollectionProvider = actionDescriptorCollectionProvider; + } + public bool Match( HttpContext httpContext, IRouter route, @@ -23,16 +41,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); @@ -49,7 +57,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing var value = obj as string; if (value != null) { - var allValues = GetAndCacheAllMatchingValues(routeKey, httpContext); + var actionDescriptors = GetAndValidateActionDescriptors(httpContext); + + var allValues = GetAndCacheAllMatchingValues(routeKey, actionDescriptors); foreach (var existingValue in allValues) { if (string.Equals(value, existingValue, StringComparison.OrdinalIgnoreCase)) @@ -63,9 +73,36 @@ namespace Microsoft.AspNetCore.Mvc.Routing return false; } - private string[] GetAndCacheAllMatchingValues(string routeKey, HttpContext httpContext) + private ActionDescriptorCollection GetAndValidateActionDescriptors(HttpContext httpContext) + { + var actionDescriptorsProvider = _actionDescriptorCollectionProvider; + + if (actionDescriptorsProvider == null) + { + // Only validate that HttpContext was passed to constraint if it is needed + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + var services = httpContext.RequestServices; + actionDescriptorsProvider = services.GetRequiredService(); + } + + var actionDescriptors = actionDescriptorsProvider.ActionDescriptors; + if (actionDescriptors == null) + { + throw new InvalidOperationException( + Resources.FormatPropertyOfTypeCannotBeNull( + nameof(IActionDescriptorCollectionProvider.ActionDescriptors), + actionDescriptorsProvider.GetType())); + } + + return actionDescriptors; + } + + private string[] GetAndCacheAllMatchingValues(string routeKey, ActionDescriptorCollection actionDescriptors) { - var actionDescriptors = GetAndValidateActionDescriptorCollection(httpContext); var version = actionDescriptors.Version; var valuesCollection = _cachedValuesCollection; @@ -77,8 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { var action = actionDescriptors.Items[i]; - string value; - if (action.RouteValues.TryGetValue(routeKey, out value) && + if (action.RouteValues.TryGetValue(routeKey, out var value) && !string.IsNullOrEmpty(value)) { values.Add(value); @@ -92,22 +128,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing return _cachedValuesCollection.Items; } - private static ActionDescriptorCollection GetAndValidateActionDescriptorCollection(HttpContext httpContext) - { - var services = httpContext.RequestServices; - var provider = services.GetRequiredService(); - var descriptors = provider.ActionDescriptors; - - if (descriptors == null) - { - throw new InvalidOperationException( - Resources.FormatPropertyOfTypeCannotBeNull("ActionDescriptors", - provider.GetType())); - } - - return descriptors; - } - private class RouteValuesCollection { public RouteValuesCollection(int version, string[] items) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index be31acba0b..fe5d8dbbf7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -180,7 +180,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal var actionDescriptorCollection = GetActionDescriptorCollection( new { controller = "TestController", action = "TestAction", area = "TestArea" }); var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); - dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); + + var services = new ServiceCollection(); + services.AddRouting(); + services.AddSingleton(actionDescriptorCollection); + + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + services.Configure(routeOptionsSetup.Configure); + + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider())); // Act var endpoints = dataSource.Endpoints; @@ -719,13 +727,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal Array.Empty()); } - var serviceProviderMock = new Mock(); - serviceProviderMock.Setup(m => m.GetService(typeof(IActionDescriptorCollectionProvider))).Returns(actionDescriptorCollectionProvider); + var services = new ServiceCollection(); + services.AddSingleton(actionDescriptorCollectionProvider); var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - serviceProviderMock.Object); + services.BuildServiceProvider()); return dataSource; } @@ -735,15 +743,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal string template, RouteValueDictionary defaults = null, IDictionary constraints = null, - RouteValueDictionary dataTokens = null) + RouteValueDictionary dataTokens = null, + IServiceProvider serviceProvider = null) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddRouting(); + if (serviceProvider == null) + { + var serviceCollection = new ServiceCollection(); + serviceCollection.AddRouting(); - var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); - serviceCollection.Configure(routeOptionsSetup.Configure); + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + serviceCollection.Configure(routeOptionsSetup.Configure); - var serviceProvider = serviceCollection.BuildServiceProvider(); + serviceProvider = serviceCollection.BuildServiceProvider(); + } var parameterPolicyFactory = serviceProvider.GetRequiredService(); return new MvcEndpointInfo(name, template, defaults, constraints, dataTokens, parameterPolicyFactory); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs index 1e1d1f8951..5aecf45a77 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -10,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; using Moq; using Xunit; @@ -17,7 +17,47 @@ namespace Microsoft.AspNetCore.Mvc.Routing { public class KnownRouteValueConstraintTests { +#pragma warning disable CS0618 // Type or member is obsolete private readonly IRouteConstraint _constraint = new KnownRouteValueConstraint(); +#pragma warning restore CS0618 // Type or member is obsolete + + [Fact] + public void ResolveFromServices_InjectsServiceProvider_HttpContextNotNeeded() + { + // Arrange + var actionDescriptor = CreateActionDescriptor("testArea", + "testController", + "testAction"); + actionDescriptor.RouteValues.Add("randomKey", "testRandom"); + var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + + var services = new ServiceCollection(); + services.AddRouting(); + services.AddSingleton(descriptorCollectionProvider); + + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + services.Configure(routeOptionsSetup.Configure); + + var serviceProvider = services.BuildServiceProvider(); + + var inlineConstraintResolver = serviceProvider.GetRequiredService(); + var constraint = inlineConstraintResolver.ResolveConstraint("exists"); + + var values = new RouteValueDictionary() + { + { "area", "testArea" }, + { "controller", "testController" }, + { "action", "testAction" }, + { "randomKey", "testRandom" } + }; + + // Act + var knownRouteValueConstraint = Assert.IsType(constraint); + var match = knownRouteValueConstraint.Match(httpContext: null, route: null, "area", values, RouteDirection.IncomingRequest); + + // Assert + Assert.True(match); + } [Theory] [InlineData("area", RouteDirection.IncomingRequest)] @@ -55,8 +95,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing { // Arrange var actionDescriptor = CreateActionDescriptor("testArea", - "testController", - "testAction"); + "testController", + "testAction"); actionDescriptor.RouteValues.Add("randomKey", "testRandom"); var httpContext = GetHttpContext(actionDescriptor); var route = Mock.Of(); @@ -115,8 +155,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing public void RouteValue_IsNotAString_MatchFails(RouteDirection direction) { var actionDescriptor = CreateActionDescriptor("testArea", - controller: null, - action: null); + controller: null, + action: null); var httpContext = GetHttpContext(actionDescriptor); var route = Mock.Of(); var values = new RouteValueDictionary() @@ -157,7 +197,57 @@ namespace Microsoft.AspNetCore.Mvc.Routing ex.Message); } - private static HttpContext GetHttpContext(ActionDescriptor actionDescriptor) + [Theory] + [InlineData("area", RouteDirection.IncomingRequest)] + [InlineData("controller", RouteDirection.IncomingRequest)] + [InlineData("action", RouteDirection.IncomingRequest)] + [InlineData("randomKey", RouteDirection.IncomingRequest)] + [InlineData("area", RouteDirection.UrlGeneration)] + [InlineData("controller", RouteDirection.UrlGeneration)] + [InlineData("action", RouteDirection.UrlGeneration)] + [InlineData("randomKey", RouteDirection.UrlGeneration)] + public void ServiceInjected_RouteKey_Exists_MatchSucceeds(string keyName, RouteDirection direction) + { + // Arrange + var actionDescriptor = CreateActionDescriptor("testArea", + "testController", + "testAction"); + actionDescriptor.RouteValues.Add("randomKey", "testRandom"); + + var provider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + + var constraint = new KnownRouteValueConstraint(provider); + + var values = new RouteValueDictionary() + { + { "area", "testArea" }, + { "controller", "testController" }, + { "action", "testAction" }, + { "randomKey", "testRandom" } + }; + + // Act + var match = constraint.Match(httpContext: null, route: null, keyName, values, direction); + + // Assert + Assert.True(match); + } + + private static HttpContext GetHttpContext(ActionDescriptor actionDescriptor, bool setupRequestServices = true) + { + var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + + var context = new Mock(); + if (setupRequestServices) + { + context.Setup(o => o.RequestServices + .GetService(typeof(IActionDescriptorCollectionProvider))) + .Returns(descriptorCollectionProvider); + } + return context.Object; + } + + private static IActionDescriptorCollectionProvider CreateActionDesciprtorCollectionProvider(ActionDescriptor actionDescriptor) { var actionProvider = new Mock(MockBehavior.Strict); @@ -176,12 +266,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var descriptorCollectionProvider = new DefaultActionDescriptorCollectionProvider( new[] { actionProvider.Object }, Enumerable.Empty()); - - var context = new Mock(); - context.Setup(o => o.RequestServices - .GetService(typeof(IActionDescriptorCollectionProvider))) - .Returns(descriptorCollectionProvider); - return context.Object; + return descriptorCollectionProvider; } private static ActionDescriptor CreateActionDescriptor(string area, string controller, string action) From 82a01a414dfd69de6c407951c917ba397b210be8 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 28 Aug 2018 16:48:45 -0700 Subject: [PATCH 202/316] Set trace id in ProblemDetalsClientErrorFactory --- .../ProblemDetailsClientErrorFactory.cs | 10 ++ .../Internal/ApiBehaviorOptionsSetup.cs | 12 +- .../ProblemDetalsClientErrorFactoryTest.cs | 68 +++++++++++- .../Internal/ApiBehaviorOptionsSetupTest.cs | 62 +++++++++++ .../ActivityReplacer.cs | 25 +++++ .../ApiBehaviorTest.cs | 105 +++++++++++------- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 1 + ...ontractSerializerFormattersWrappingTest.cs | 48 +++++--- .../XmlSerializerFormattersWrappingTest.cs | 48 +++++--- 9 files changed, 307 insertions(+), 72 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs index 1bf1e3ac43..ff47a18fa6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs @@ -2,12 +2,14 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Infrastructure { internal class ProblemDetailsClientErrorFactory : IClientErrorFactory { + private static readonly string TraceIdentifierKey = "traceId"; private readonly ApiBehaviorOptions _options; public ProblemDetailsClientErrorFactory(IOptions options) @@ -28,6 +30,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { problemDetails.Title = errorData.Title; problemDetails.Type = errorData.Link; + + SetTraceId(actionContext, problemDetails); } return new ObjectResult(problemDetails) @@ -40,5 +44,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure }, }; } + + internal static void SetTraceId(ActionContext actionContext, ProblemDetails problemDetails) + { + var traceId = Activity.Current?.Id ?? actionContext.HttpContext.TraceIdentifier; + problemDetails.Extensions[TraceIdentifierKey] = traceId; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs index 4d64d9bb84..bddaaa2478 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Logging; @@ -128,9 +129,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal return result; } - private static IActionResult ProblemDetailsInvalidModelStateResponse(ActionContext context) + internal static IActionResult ProblemDetailsInvalidModelStateResponse(ActionContext context) { - var result = new BadRequestObjectResult(new ValidationProblemDetails(context.ModelState)); + var problemDetails = new ValidationProblemDetails(context.ModelState) + { + Status = StatusCodes.Status400BadRequest, + }; + + ProblemDetailsClientErrorFactory.SetTraceId(context, problemDetails); + + var result = new BadRequestObjectResult(problemDetails); result.ContentTypes.Add("application/problem+json"); result.ContentTypes.Add("application/problem+xml"); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs index 65a53a8158..603b60e381 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs @@ -1,6 +1,8 @@ // 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.Diagnostics; +using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Options; using Xunit; @@ -22,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure })); // Act - var result = factory.GetClientError(new ActionContext(), clientError); + var result = factory.GetClientError(GetActionContext(), clientError); // Assert var objectResult = Assert.IsType(result); @@ -49,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure })); // Act - var result = factory.GetClientError(new ActionContext(), clientError); + var result = factory.GetClientError(GetActionContext(), clientError); // Assert var objectResult = Assert.IsType(result); @@ -61,5 +63,67 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure Assert.Null(problemDetails.Detail); Assert.Null(problemDetails.Instance); } + + [Fact] + public void GetClientError_UsesActivityId_ToSetTraceId() + { + // Arrange + using (new ActivityReplacer()) + { + var clientError = new UnsupportedMediaTypeResult(); + var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions + { + ClientErrorMapping = + { + [415] = new ClientErrorData { Link = "Some link", Title = "Summary" }, + }, + })); + + // Act + var result = factory.GetClientError(GetActionContext(), clientError); + + // Assert + var objectResult = Assert.IsType(result); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes); + var problemDetails = Assert.IsType(objectResult.Value); + + Assert.Equal(Activity.Current.Id, problemDetails.Extensions["traceId"]); + } + } + + [Fact] + public void GetClientError_UsesHttpContext_ToSetTraceIdIfActivityIdIsNotSet() + { + // Arrange + var clientError = new UnsupportedMediaTypeResult(); + var factory = new ProblemDetailsClientErrorFactory(Options.Create(new ApiBehaviorOptions + { + ClientErrorMapping = + { + [415] = new ClientErrorData { Link = "Some link", Title = "Summary" }, + }, + })); + + // Act + var result = factory.GetClientError(GetActionContext(), clientError); + + // Assert + var objectResult = Assert.IsType(result); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, objectResult.ContentTypes); + var problemDetails = Assert.IsType(objectResult.Value); + + Assert.Equal("42", problemDetails.Extensions["traceId"]); + } + + private static ActionContext GetActionContext() + { + return new ActionContext + { + HttpContext = new DefaultHttpContext + { + TraceIdentifier = "42", + } + }; + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs index 622a36e5ff..4ae53cd4d7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs @@ -2,6 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; +using System.Linq; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; @@ -97,5 +100,64 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Same(expected, options.InvalidModelStateResponseFactory); } + + [Fact] + public void ProblemDetailsInvalidModelStateResponse_ReturnsBadRequestWithProblemDetails() + { + // Arrange + var actionContext = new ActionContext + { + HttpContext = new DefaultHttpContext { TraceIdentifier = "42" }, + }; + + // Act + var result = ApiBehaviorOptionsSetup.ProblemDetailsInvalidModelStateResponse(actionContext); + + // Assert + var badRequest = Assert.IsType(result); + Assert.Equal(new[] { "application/problem+json", "application/problem+xml" }, badRequest.ContentTypes.OrderBy(c => c)); + + var problemDetails = Assert.IsType(badRequest.Value); + Assert.Equal(400, problemDetails.Status); + } + + [Fact] + public void ProblemDetailsInvalidModelStateResponse_SetsTraceId() + { + // Arrange + using (new ActivityReplacer()) + { + var actionContext = new ActionContext + { + HttpContext = new DefaultHttpContext { TraceIdentifier = "42" }, + }; + + // Act + var result = ApiBehaviorOptionsSetup.ProblemDetailsInvalidModelStateResponse(actionContext); + + // Assert + var badRequest = Assert.IsType(result); + var problemDetails = Assert.IsType(badRequest.Value); + Assert.Equal(Activity.Current.Id, problemDetails.Extensions["traceId"]); + } + } + + [Fact] + public void ProblemDetailsInvalidModelStateResponse_SetsTraceIdFromRequest_IfActivityIsNull() + { + // Arrange + var actionContext = new ActionContext + { + HttpContext = new DefaultHttpContext { TraceIdentifier = "42" }, + }; + + // Act + var result = ApiBehaviorOptionsSetup.ProblemDetailsInvalidModelStateResponse(actionContext); + + // Assert + var badRequest = Assert.IsType(result); + var problemDetails = Assert.IsType(badRequest.Value); + Assert.Equal("42", problemDetails.Extensions["traceId"]); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs new file mode 100644 index 0000000000..f7bc9d8193 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs @@ -0,0 +1,25 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; + +namespace Microsoft.AspNetCore.Mvc +{ + public class ActivityReplacer : IDisposable + { + private readonly Activity _activity; + + public ActivityReplacer() + { + _activity = new Activity("Test"); + _activity.Start(); + } + + public void Dispose() + { + Debug.Assert(Activity.Current == _activity); + _activity.Stop(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs index 127d612fdd..c30dd30750 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Net; using System.Net.Http; @@ -35,37 +36,48 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ActionsReturnBadRequest_WhenModelStateIsInvalid() { // Arrange - var contactModel = new Contact + using (new ActivityReplacer()) { - Name = "Abc", - City = "Redmond", - State = "WA", - Zip = "Invalid", - }; - var contactString = JsonConvert.SerializeObject(contactModel); - - // Act - var response = await Client.PostAsJsonAsync("/contact", contactModel); - - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); - Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType); - var problemDetails = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); - Assert.Collection( - problemDetails.Errors.OrderBy(kvp => kvp.Key), - kvp => + var contactModel = new Contact { - Assert.Equal("Name", kvp.Key); - var error = Assert.Single(kvp.Value); - Assert.Equal("The field Name must be a string with a minimum length of 5 and a maximum length of 30.", error); - }, - kvp => - { - Assert.Equal("Zip", kvp.Key); - var error = Assert.Single(kvp.Value); - Assert.Equal("The field Zip must match the regular expression '\\d{5}'.", error); - } - ); + Name = "Abc", + City = "Redmond", + State = "WA", + Zip = "Invalid", + }; + var contactString = JsonConvert.SerializeObject(contactModel); + + // Act + var response = await Client.PostAsJsonAsync("/contact", contactModel); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + Assert.Equal("application/problem+json", response.Content.Headers.ContentType.MediaType); + var problemDetails = JsonConvert.DeserializeObject(await response.Content.ReadAsStringAsync()); + Assert.Collection( + problemDetails.Errors.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("Name", kvp.Key); + var error = Assert.Single(kvp.Value); + Assert.Equal("The field Name must be a string with a minimum length of 5 and a maximum length of 30.", error); + }, + kvp => + { + Assert.Equal("Zip", kvp.Key); + var error = Assert.Single(kvp.Value); + Assert.Equal("The field Zip must match the regular expression '\\d{5}'.", error); + } + ); + + Assert.Collection( + problemDetails.Extensions, + kvp => + { + Assert.Equal("traceId", kvp.Key); + Assert.Equal(Activity.Current.Id, kvp.Value); + }); + } } [Fact] @@ -253,21 +265,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [Fact] public async Task ClientErrorResultFilterExecutesForStatusCodeResults() { - // Act - var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult"); + using (new ActivityReplacer()) + { + // Act + var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult"); - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); - var content = await response.Content.ReadAsStringAsync(); - var problemDetails = JsonConvert.DeserializeObject(content); - Assert.Equal(404, problemDetails.Status); + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + var problemDetails = JsonConvert.DeserializeObject(content); + Assert.Equal(404, problemDetails.Status); + Assert.Collection( + problemDetails.Extensions, + kvp => + { + Assert.Equal("traceId", kvp.Key); + Assert.Equal(Activity.Current.Id, kvp.Value); + }); + } } [Fact] public async Task SerializingProblemDetails_IgnoresNullValuedProperties() { // Arrange - var expected = new[] { "status", "title", "type" }; + var expected = new[] { "status", "title", "traceId", "type" }; // Act var response = await Client.GetAsync("/contact/ActionReturningStatusCodeResult"); @@ -310,7 +332,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("Error", validationProblemDetails.Title); Assert.Equal(400, validationProblemDetails.Status); - Assert.Equal("27", validationProblemDetails.Extensions["tracking-id"]); + Assert.Collection( + validationProblemDetails.Extensions, + kvp => + { + Assert.Equal("tracking-id", kvp.Key); + Assert.Equal("27", kvp.Value); + }); + Assert.Collection( validationProblemDetails.Errors, kvp => diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 1e7cb2f215..bce36312a4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -10,6 +10,7 @@ + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs index 9ee16921a6..2e34fbbe27 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs @@ -1,6 +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 System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -213,15 +214,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ProblemDetails_IsSerialized() { // Arrange - var expected = @"404Not Foundhttps://tools.ietf.org/html/rfc7231#section-6.5.4"; + using (new ActivityReplacer()) + { + var expected = "" + + "404" + + "Not Found" + + "https://tools.ietf.org/html/rfc7231#section-6.5.4" + + $"{Activity.Current.Id}" + + ""; - // Act - var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningClientErrorStatusCodeResult"); + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningClientErrorStatusCodeResult"); - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); - var content = await response.Content.ReadAsStringAsync(); - XmlAssert.Equal(expected, content); + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } [Fact] @@ -244,16 +253,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ValidationProblemDetails_IsSerialized() { // Arrange - var expected = @"400One or more validation errors occurred. -The State field is required."; + using (new ActivityReplacer()) + { + var expected = "" + + "400" + + "One or more validation errors occurred." + + $"{Activity.Current.Id}" + + "" + + "The State field is required." + + "" + + ""; - // Act - var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationProblem"); + // Act + var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationProblem"); - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); - var content = await response.Content.ReadAsStringAsync(); - XmlAssert.Equal(expected, content); + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs index d54cd3880c..b8b41c5f0a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs @@ -1,6 +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 System.Diagnostics; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -188,15 +189,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ProblemDetails_IsSerialized() { // Arrange - var expected = @"404Not Foundhttps://tools.ietf.org/html/rfc7231#section-6.5.4"; + using (new ActivityReplacer()) + { + var expected = "" + + "404" + + "Not Found" + + "https://tools.ietf.org/html/rfc7231#section-6.5.4" + + $"{Activity.Current.Id}" + + ""; - // Act - var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningClientErrorStatusCodeResult"); + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningClientErrorStatusCodeResult"); - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); - var content = await response.Content.ReadAsStringAsync(); - XmlAssert.Equal(expected, content); + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } [Fact] @@ -219,16 +228,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ValidationProblemDetails_IsSerialized() { // Arrange - var expected = @"400One or more validation errors occurred. -The State field is required."; + using (new ActivityReplacer()) + { + var expected = "" + + "400" + + "One or more validation errors occurred." + + $"{Activity.Current.Id}" + + "" + + "The State field is required." + + "" + + ""; - // Act - var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationProblem"); + // Act + var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationProblem"); - // Assert - await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); - var content = await response.Content.ReadAsStringAsync(); - XmlAssert.Equal(expected, content); + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } [Fact] From 7bd9f9cc3e11c0e9686453f4cad21ac6bf7ff04a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 29 Aug 2018 03:32:20 +0100 Subject: [PATCH 203/316] Reduce IList interface calls --- .../Internal/PagedBufferedTextWriter.cs | 3 +- .../Internal/PagedCharBuffer.cs | 28 +++++++++++-------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs index 5dce67153b..9148831b6e 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs @@ -37,7 +37,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } var pages = _charBuffer.Pages; - for (var i = 0; i < pages.Count; i++) + var count = pages.Count; + for (var i = 0; i < count; i++) { var page = pages[i]; var pageLength = Math.Min(length, page.Length); diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs index f086daf67b..12dcce04ea 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs @@ -11,6 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public const int PageSize = 1024; private int _charIndex; + private List _pages = new List(); public PagedCharBuffer(ICharBufferSource bufferSource) { @@ -19,16 +20,18 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public ICharBufferSource BufferSource { get; } - public IList Pages { get; } = new List(); + public IList Pages => _pages; public int Length { get { var length = _charIndex; - for (var i = 0; i < Pages.Count - 1; i++) + var pages = _pages; + var fullPages = pages.Count - 1; + for (var i = 0; i < fullPages; i++) { - length += Pages[i].Length; + length += pages[i].Length; } return length; @@ -100,13 +103,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal /// public void Clear() { - for (var i = Pages.Count - 1; i > 0; i--) + var pages = _pages; + for (var i = pages.Count - 1; i > 0; i--) { - var page = Pages[i]; + var page = pages[i]; try { - Pages.RemoveAt(i); + pages.RemoveAt(i); } finally { @@ -115,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } _charIndex = 0; - CurrentPage = Pages.Count > 0 ? Pages[0] : null; + CurrentPage = pages.Count > 0 ? pages[0] : null; } private char[] GetCurrentPage() @@ -135,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal try { page = BufferSource.Rent(PageSize); - Pages.Add(page); + _pages.Add(page); } catch when (page != null) { @@ -148,12 +152,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public void Dispose() { - for (var i = 0; i < Pages.Count; i++) + var pages = _pages; + var count = pages.Count; + for (var i = 0; i < count; i++) { - BufferSource.Return(Pages[i]); + BufferSource.Return(pages[i]); } - Pages.Clear(); + pages.Clear(); } } } From 22a40b6f2bee8fd96874bf0fa0daa94fcf6a6710 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 29 Aug 2018 04:42:53 +0100 Subject: [PATCH 204/316] Use Pages as List --- .../Internal/PagedCharBuffer.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs index 12dcce04ea..0c4931c7b9 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs @@ -11,7 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public const int PageSize = 1024; private int _charIndex; - private List _pages = new List(); public PagedCharBuffer(ICharBufferSource bufferSource) { @@ -20,14 +19,15 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public ICharBufferSource BufferSource { get; } - public IList Pages => _pages; + // Strongly typed rather than IList for performance + public List Pages { get; } = new List(); public int Length { get { var length = _charIndex; - var pages = _pages; + var pages = Pages; var fullPages = pages.Count - 1; for (var i = 0; i < fullPages; i++) { @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal /// public void Clear() { - var pages = _pages; + var pages = Pages; for (var i = pages.Count - 1; i > 0; i--) { var page = pages[i]; @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal try { page = BufferSource.Rent(PageSize); - _pages.Add(page); + Pages.Add(page); } catch when (page != null) { @@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public void Dispose() { - var pages = _pages; + var pages = Pages; var count = pages.Count; for (var i = 0; i < count; i++) { From c7f6e7ab2fe15affac25cceb0fdb0df127187082 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Wed, 29 Aug 2018 04:52:30 +0100 Subject: [PATCH 205/316] Grumpy XUnit --- .../Internal/PagedCharBufferTest.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs index 28944150ff..8c05f896f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs @@ -469,14 +469,14 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal // Assert - 1 Assert.Equal(0, buffer.Length); - Assert.Equal(1, buffer.Pages.Count); + Assert.Single(buffer.Pages); // Act - 2 buffer.Append("efgh"); // Assert - 2 Assert.Equal(4, buffer.Length); - Assert.Equal(1, buffer.Pages.Count); + Assert.Single(buffer.Pages); Assert.Equal(new[] { 'e', 'f', 'g', 'h' }, buffer.Pages[0].Take(buffer.Length)); } } From 2a426dfea5982bf654a5dd0cedf138bdfc233afc Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 30 Aug 2018 01:07:45 +0100 Subject: [PATCH 206/316] Make ViewBuffer methods more inlinable (#8339) * Make ViewBuffer methods more inlinable --- .../Internal/ViewBuffer.cs | 45 +++++++++++++------ .../Internal/ViewBufferPage.cs | 4 ++ 2 files changed, 36 insertions(+), 13 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs index 5143e515d4..7a39276b7b 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Html; @@ -90,55 +91,73 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } /// + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder Append(string unencoded) { - if (unencoded == null) + if (unencoded != null) { - return this; + // Text that needs encoding is the uncommon case in views, which is why it + // creates a wrapper and pre-encoded text does not. + AppendValue(new ViewBufferValue(new EncodingWrapper(unencoded))); } - // Text that needs encoding is the uncommon case in views, which is why it - // creates a wrapper and pre-encoded text does not. - AppendValue(new ViewBufferValue(new EncodingWrapper(unencoded))); return this; } /// + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder AppendHtml(IHtmlContent content) { - if (content == null) + if (content != null) { - return this; + AppendValue(new ViewBufferValue(content)); } - AppendValue(new ViewBufferValue(content)); return this; } /// + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder AppendHtml(string encoded) { - if (encoded == null) + if (encoded != null) { - return this; + AppendValue(new ViewBufferValue(encoded)); } - AppendValue(new ViewBufferValue(encoded)); return this; } + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AppendValue(ViewBufferValue value) { var page = GetCurrentPage(); page.Append(value); } + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] private ViewBufferPage GetCurrentPage() { - if (_currentPage == null || _currentPage.IsFull) + var currentPage = _currentPage; + if (currentPage == null || currentPage.IsFull) { - AddPage(new ViewBufferPage(_bufferScope.GetPage(_pageSize))); + // Uncommon slow-path + return AppendNewPage(); } + + return currentPage; + } + + // Slow path for above, don't inline + [MethodImpl(MethodImplOptions.NoInlining)] + private ViewBufferPage AppendNewPage() + { + AddPage(new ViewBufferPage(_bufferScope.GetPage(_pageSize))); return _currentPage; } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs index b561e71825..416e329bd7 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs @@ -1,6 +1,8 @@ // 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.Runtime.CompilerServices; + namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal { public class ViewBufferPage @@ -18,6 +20,8 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public bool IsFull => Count == Capacity; + // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(ViewBufferValue value) => Buffer[Count++] = value; } } From ffdbea9dc1aa5888ddca3db24b15864e6eae4e01 Mon Sep 17 00:00:00 2001 From: Kristian Hellang Date: Wed, 8 Aug 2018 12:50:46 +0200 Subject: [PATCH 207/316] Add analyzer support for status code methods and constructors --- .../CodeAnalysisExtensions.cs | 2 +- .../MvcFacts.cs | 5 + .../ApiControllerSymbolCache.cs | 10 + .../ApiSymbolNames.cs | 4 + .../SymbolApiResponseMetadataProvider.cs | 173 +++++++++++++++++- .../ControllerBase.cs | 11 +- .../StatusCodeValueAttribute.cs | 12 ++ .../StatusCodeResult.cs | 2 +- ...AttributeCodeFixProviderIntegrationTest.cs | 12 ++ ...ForNonExistingStatusCodeConstants.Input.cs | 17 ++ ...orNonExistingStatusCodeConstants.Output.cs | 22 +++ ...tusCodesFromConstructorParameters.Input.cs | 38 ++++ ...usCodesFromConstructorParameters.Output.cs | 44 +++++ ...dsStatusCodesFromMethodParameters.Input.cs | 38 ++++ ...sStatusCodesFromMethodParameters.Output.cs | 44 +++++ ...sStatusCodesFromObjectInitializer.Input.cs | 51 ++++++ ...StatusCodesFromObjectInitializer.Output.cs | 57 ++++++ 17 files changed, 526 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs index 7856b27b02..72da1b1f4c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs @@ -123,7 +123,7 @@ namespace Microsoft.CodeAnalysis return false; } - private static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) + public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) { foreach (var declaredAttribute in symbol.GetAttributes()) { diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs index 90651247c4..fc06eda6bb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs @@ -116,6 +116,11 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers return false; } + if (!method.ReturnsVoid) + { + return false; + } + if (method.Parameters.Length != disposableDispose.Parameters.Length) { return false; diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs index c2a6f167d2..e92c0927ca 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers @@ -24,6 +25,11 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesDefaultResponseTypeAttribute); ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesResponseTypeAttribute); + StatusCodeValueAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.StatusCodeValueAttribute); + + var statusCodeActionResult = compilation.GetTypeByMetadataName(ApiSymbolNames.IStatusCodeActionResult); + StatusCodeActionResultStatusProperty = (IPropertySymbol)statusCodeActionResult.GetMembers("StatusCode")[0]; + var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); var members = disposable.GetMembers(nameof(IDisposable.Dispose)); IDisposableDispose = members.Length == 1 ? (IMethodSymbol)members[0] : null; @@ -47,6 +53,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public IMethodSymbol IDisposableDispose { get; } + public IPropertySymbol StatusCodeActionResultStatusProperty { get; } + public ITypeSymbol ModelStateDictionary { get; } public INamedTypeSymbol NonActionAttribute { get; } @@ -56,5 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public INamedTypeSymbol ProducesDefaultResponseTypeAttribute { get; } public INamedTypeSymbol ProducesResponseTypeAttribute { get; } + + public INamedTypeSymbol StatusCodeValueAttribute { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs index 0771fafc22..563c3ad570 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs @@ -23,6 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; + public const string IStatusCodeActionResult = "Microsoft.AspNetCore.Mvc.Infrastructure.IStatusCodeActionResult"; + public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; @@ -34,5 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; public const string HttpStatusCodes = "Microsoft.AspNetCore.Http.StatusCodes"; + + public const string StatusCodeValueAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.StatusCodeValueAttribute"; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs index 5de70bae0a..7e3744757e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -61,7 +61,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } return Array.Empty(); - } + } + private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method) { var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true) @@ -222,6 +223,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) { + if (returnStatementSyntax.IsMissing || returnStatementSyntax.Expression.IsMissing) + { + // Ignore malformed return statements. + continue; + } + var responseMetadata = InspectReturnStatementSyntax( symbolCache, semanticModel, @@ -248,11 +255,6 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers CancellationToken cancellationToken) { var returnExpression = returnStatementSyntax.Expression; - if (returnExpression.IsMissing) - { - return null; - } - var typeInfo = semanticModel.GetTypeInfo(returnExpression, cancellationToken); if (typeInfo.Type.TypeKind == TypeKind.Error) { @@ -267,25 +269,176 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers if (defaultStatusCodeAttribute != null) { - var statusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); - if (statusCode == null) + var defaultStatusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); + if (defaultStatusCode == null) { // Unable to read the status code even though the attribute exists. return null; } - return new ActualApiResponseMetadata(returnStatementSyntax, statusCode.Value); + return new ActualApiResponseMetadata(returnStatementSyntax, defaultStatusCode.Value); } - else if (!symbolCache.IActionResult.IsAssignableFrom(statementReturnType)) + + if (!symbolCache.IActionResult.IsAssignableFrom(statementReturnType)) { // Return expression does not have a DefaultStatusCodeAttribute and it is not // an instance of IActionResult. Must be returning the "model". return new ActualApiResponseMetadata(returnStatementSyntax); } + int statusCode; + switch (returnExpression) + { + case InvocationExpressionSyntax invocation: + // Covers the 'return StatusCode(200)' case. + if (TryGetParameterStatusCode(symbolCache, semanticModel, invocation.Expression, invocation.ArgumentList, cancellationToken, out statusCode)) + { + return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); + } + break; + + case ObjectCreationExpressionSyntax creation: + // Covers the 'return new ObjectResult(...) { StatusCode = 200 }' case. + if (TryGetInitializerStatusCode(symbolCache, semanticModel, creation.Initializer, cancellationToken, out statusCode)) + { + return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); + } + + // Covers the 'return new StatusCodeResult(200) case. + if (TryGetParameterStatusCode(symbolCache, semanticModel, creation, creation.ArgumentList, cancellationToken, out statusCode)) + { + return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); + } + break; + } + return null; } + private static bool TryGetInitializerStatusCode( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + InitializerExpressionSyntax initializer, + CancellationToken cancellationToken, + out int statusCode) + { + if (initializer == null) + { + statusCode = default; + return false; + } + + for (var i = 0; i < initializer.Expressions.Count; i++) + { + if (!(initializer.Expressions[i] is AssignmentExpressionSyntax assignment)) + { + continue; + } + + if (assignment.Left is IdentifierNameSyntax identifier) + { + var symbolInfo = semanticModel.GetSymbolInfo(identifier, cancellationToken); + + if (symbolInfo.Symbol is IPropertySymbol property && IsInterfaceImplementation(property, symbolCache.StatusCodeActionResultStatusProperty)) + { + return TryGetExpressionStatusCode(semanticModel, assignment.Right, cancellationToken, out statusCode); + } + } + } + + statusCode = default; + return false; + } + + private static bool IsInterfaceImplementation(IPropertySymbol property, IPropertySymbol statusCodeActionResultStatusProperty) + { + if (property.Name != statusCodeActionResultStatusProperty.Name) + { + return false; + } + + for (var i = 0; i < property.ExplicitInterfaceImplementations.Length; i++) + { + if (property.ExplicitInterfaceImplementations[i] == statusCodeActionResultStatusProperty) + { + return true; + } + } + + var implementedProperty = property.ContainingType.FindImplementationForInterfaceMember(statusCodeActionResultStatusProperty); + return implementedProperty == property; + } + + private static bool TryGetParameterStatusCode( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + ExpressionSyntax expression, + BaseArgumentListSyntax argumentList, + CancellationToken cancellationToken, + out int statusCode) + { + var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); + + if (!(symbolInfo.Symbol is IMethodSymbol method)) + { + statusCode = default; + return false; + } + + for (var i = 0; i < method.Parameters.Length; i++) + { + var parameter = method.Parameters[i]; + if (!parameter.HasAttribute(symbolCache.StatusCodeValueAttribute)) + { + continue; + } + + + var argument = argumentList.Arguments[parameter.Ordinal]; + return TryGetExpressionStatusCode(semanticModel, argument.Expression, cancellationToken, out statusCode); + } + + statusCode = default; + return false; + } + + private static bool TryGetExpressionStatusCode( + SemanticModel semanticModel, + ExpressionSyntax expression, + CancellationToken cancellationToken, + out int statusCode) + { + if (expression is LiteralExpressionSyntax literal && literal.Token.Value is int literalStatusCode) + { + // Covers the 'return StatusCode(200)' case. + statusCode = literalStatusCode; + return true; + } + + if (expression is IdentifierNameSyntax || expression is MemberAccessExpressionSyntax) + { + var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); + + if (symbolInfo.Symbol is IFieldSymbol field && field.HasConstantValue && field.ConstantValue is int constantStatusCode) + { + // Covers the 'return StatusCode(StatusCodes.Status200OK)' case. + // It also covers the 'return StatusCode(StatusCode)' case, where 'StatusCode' is a constant field. + statusCode = constantStatusCode; + return true; + } + + if (symbolInfo.Symbol is ILocalSymbol local && local.HasConstantValue && local.ConstantValue is int localStatusCode) + { + // Covers the 'return StatusCode(statusCode)' case, where 'statusCode' is a local constant. + statusCode = localStatusCode; + return true; + } + } + + statusCode = default; + return false; + } + private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) { return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs index c51f809da2..520f8a5fbe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs @@ -10,6 +10,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; @@ -200,7 +201,7 @@ namespace Microsoft.AspNetCore.Mvc /// The status code to set on the response. /// The created object for the response. [NonAction] - public virtual StatusCodeResult StatusCode(int statusCode) + public virtual StatusCodeResult StatusCode([StatusCodeValue] int statusCode) => new StatusCodeResult(statusCode); /// @@ -210,10 +211,12 @@ namespace Microsoft.AspNetCore.Mvc /// The value to set on the . /// The created object for the response. [NonAction] - public virtual ObjectResult StatusCode(int statusCode, object value) + public virtual ObjectResult StatusCode([StatusCodeValue] int statusCode, object value) { - var result = new ObjectResult(value); - result.StatusCode = statusCode; + var result = new ObjectResult(value) + { + StatusCode = statusCode + }; return result; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs new file mode 100644 index 0000000000..944f958d62 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs @@ -0,0 +1,12 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + internal sealed class StatusCodeValueAttribute : Attribute + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs index c3ec5614c0..c95b69237e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc /// with the given . /// /// The HTTP status code of the response. - public StatusCodeResult(int statusCode) + public StatusCodeResult([StatusCodeValue] int statusCode) { StatusCode = statusCode; } diff --git a/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs index beea4d68a7..755fe9b136 100644 --- a/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs @@ -33,6 +33,18 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task CodeFixAddsFullyQualifiedProducesResponseType() => RunTest(); + [Fact] + public Task CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants() => RunTest(); + + [Fact] + public Task CodeFixAddsStatusCodesFromMethodParameters() => RunTest(); + + [Fact] + public Task CodeFixAddsStatusCodesFromConstructorParameters() => RunTest(); + + [Fact] + public Task CodeFixAddsStatusCodesFromObjectInitializer() => RunTest(); + private async Task RunTest([CallerMemberName] string testMethod = "") { // Arrange diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs new file mode 100644 index 0000000000..93668878b1 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs @@ -0,0 +1,17 @@ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsNumericLiteralForNonExistingStatusCodeConstantsController : ControllerBase + { + public IActionResult GetItem(int id) + { + if (id == 0) + { + return StatusCode(345); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs new file mode 100644 index 0000000000..82a8f44a3b --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs @@ -0,0 +1,22 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsNumericLiteralForNonExistingStatusCodeConstantsController : ControllerBase + { + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(345)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return StatusCode(345); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs new file mode 100644 index 0000000000..0fe4637037 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromConstructorParametersController : ControllerBase + { + private const int FieldStatusCode = 201; + + public IActionResult GetItem(int id) + { + if (id == 0) + { + return new StatusCodeResult(422); + } + + if (id == 1) + { + return new StatusCodeResult(StatusCodes.Status202Accepted); + } + + if (id == 2) + { + const int localStatusCode = 204; + + return new StatusCodeResult(localStatusCode); + } + + if (id == 3) + { + return new StatusCodeResult(FieldStatusCode); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs new file mode 100644 index 0000000000..dbdf8d7fae --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromConstructorParametersController : ControllerBase + { + private const int FieldStatusCode = 201; + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return new StatusCodeResult(422); + } + + if (id == 1) + { + return new StatusCodeResult(StatusCodes.Status202Accepted); + } + + if (id == 2) + { + const int localStatusCode = 204; + + return new StatusCodeResult(localStatusCode); + } + + if (id == 3) + { + return new StatusCodeResult(FieldStatusCode); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs new file mode 100644 index 0000000000..8bc4a372e1 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs @@ -0,0 +1,38 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromMethodParametersController : ControllerBase + { + private const int FieldStatusCode = 201; + + public IActionResult GetItem(int id) + { + if (id == 0) + { + return StatusCode(422); + } + + if (id == 1) + { + return StatusCode(StatusCodes.Status202Accepted); + } + + if (id == 2) + { + const int localStatusCode = 204; + + return StatusCode(localStatusCode); + } + + if (id == 3) + { + return StatusCode(FieldStatusCode); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs new file mode 100644 index 0000000000..d1ad5d182d --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs @@ -0,0 +1,44 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromMethodParametersController : ControllerBase + { + private const int FieldStatusCode = 201; + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return StatusCode(422); + } + + if (id == 1) + { + return StatusCode(StatusCodes.Status202Accepted); + } + + if (id == 2) + { + const int localStatusCode = 204; + + return StatusCode(localStatusCode); + } + + if (id == 3) + { + return StatusCode(FieldStatusCode); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs new file mode 100644 index 0000000000..7041164c5e --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs @@ -0,0 +1,51 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromObjectInitializerController : ControllerBase + { + private const int FieldStatusCode = 201; + + public IActionResult GetItem(int id) + { + if (id == 0) + { + return new ObjectResult(new object()) + { + StatusCode = 422 + }; + } + + if (id == 1) + { + return new ObjectResult(new object()) + { + StatusCode = StatusCodes.Status202Accepted + }; + } + + if (id == 2) + { + const int localStatusCode = 204; + + return new ObjectResult(new object()) + { + StatusCode = localStatusCode + }; + } + + if (id == 3) + { + return new ObjectResult(new object()) + { + ContentTypes = { "application/json" }, + StatusCode = FieldStatusCode + }; + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs new file mode 100644 index 0000000000..8f35baeee7 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs @@ -0,0 +1,57 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsStatusCodesFromObjectInitializerController : ControllerBase + { + private const int FieldStatusCode = 201; + + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType(StatusCodes.Status202Accepted)] + [ProducesResponseType(StatusCodes.Status204NoContent)] + [ProducesResponseType(StatusCodes.Status422UnprocessableEntity)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return new ObjectResult(new object()) + { + StatusCode = 422 + }; + } + + if (id == 1) + { + return new ObjectResult(new object()) + { + StatusCode = StatusCodes.Status202Accepted + }; + } + + if (id == 2) + { + const int localStatusCode = 204; + + return new ObjectResult(new object()) + { + StatusCode = localStatusCode + }; + } + + if (id == 3) + { + return new ObjectResult(new object()) + { + ContentTypes = { "application/json" }, + StatusCode = FieldStatusCode + }; + } + + return Ok(new object()); + } + } +} From 5cdc172b17cd339c2a686b2191d3729613ae7441 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 Aug 2018 13:28:46 +1200 Subject: [PATCH 208/316] Fix obsolete constraint resolver usage (#8361) --- .../RemoteAttributeTest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs index 5c8da2b478..5742da183b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs @@ -1045,7 +1045,7 @@ namespace Microsoft.AspNetCore.Mvc serviceCollection.AddRouting(); serviceCollection.AddSingleton( - provider => new DefaultInlineConstraintResolver(provider.GetRequiredService>())); + provider => new DefaultInlineConstraintResolver(provider.GetRequiredService>(), provider)); if (localizerFactory != null) { From 927e7c8bfc449513a25a2f6e796c684c293c2cde Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 30 Aug 2018 15:14:34 +1200 Subject: [PATCH 209/316] Support route data tokens with Endpoint Routing (#8360) --- .../Internal/MvcEndpointDataSource.cs | 26 ++++++++++++------- .../Internal/MvcEndpointDataSourceTests.cs | 23 +--------------- .../RoutingTestsBase.cs | 24 +++++++++++++++++ .../Controllers/DataTokensController.cs | 15 +++++++++++ test/WebSites/RoutingWebSite/Startup.cs | 17 ++++++++---- .../RoutingWebSite/StartupWith21Compat.cs | 17 ++++++++---- 6 files changed, 80 insertions(+), 42 deletions(-) create mode 100644 test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 2d3f5e4ba2..c99e3a216d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -174,6 +174,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, + endpointInfo.DataTokens, suppressLinkGeneration: false, suppressPathMatching: false); endpoints.Add(subEndpoint); @@ -214,6 +215,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpointInfo.Defaults, ++conventionalRouteOrder, endpointInfo, + endpointInfo.DataTokens, suppressLinkGeneration: false, suppressPathMatching: false); endpoints.Add(endpoint); @@ -229,6 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal nonInlineDefaults: null, action.AttributeRouteInfo.Order, action.AttributeRouteInfo, + dataTokens: null, suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration, suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching); endpoints.Add(endpoint); @@ -378,20 +381,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal object nonInlineDefaults, int order, object source, + RouteValueDictionary dataTokens, bool suppressLinkGeneration, bool suppressPathMatching) { RequestDelegate requestDelegate = (context) => { - var values = context.Features.Get().RouteValues; - var routeData = new RouteData(); - foreach (var kvp in values) - { - if (kvp.Value != null) - { - routeData.Values.Add(kvp.Key, kvp.Value); - } - } + var routeData = context.GetRouteData(); var actionContext = new ActionContext(context, routeData, action); @@ -407,6 +403,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal routeName, new RouteValueDictionary(action.RouteValues), source, + dataTokens, suppressLinkGeneration, suppressPathMatching); @@ -425,6 +422,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal string routeName, RouteValueDictionary requiredValues, object source, + RouteValueDictionary dataTokens, bool suppressLinkGeneration, bool suppressPathMatching) { @@ -438,6 +436,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal metadata.AddRange(action.EndpointMetadata); } + if (dataTokens != null) + { + metadata.Add(new DataTokensMetadata(dataTokens)); + } + metadata.Add(new RouteValuesAddressMetadata(routeName, requiredValues)); // Add filter descriptors to endpoint metadata @@ -506,7 +509,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal { foreach (var kvp in requiredValues) { - defaults[kvp.Key] = kvp.Value; + if (kvp.Value != null) + { + defaults[kvp.Key] = kvp.Value; + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index fe5d8dbbf7..a6f92ddc2c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -89,6 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var featureCollection = new FeatureCollection(); featureCollection.Set(endpointFeature); featureCollection.Set(endpointFeature); + featureCollection.Set(endpointFeature); var httpContextMock = new Mock(); httpContextMock.Setup(m => m.Features).Returns(featureCollection); @@ -694,28 +695,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } - [Fact] - public void RequiredValues_HavingNull_AndNotPresentInDefaultValues_IsAddedToDefaultValues() - { - // Arrange - var requiredValues = new RouteValueDictionary( - new { area = (string)null, controller = "Foo", action = "Bar", page = (string)null }); - var expectedDefaults = requiredValues; - var actionDescriptorCollection = GetActionDescriptorCollection(requiredValues: requiredValues); - var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); - dataSource.ConventionalEndpointInfos.Add( - CreateEndpointInfo(string.Empty, "{controller=Home}/{action=Index}")); - - // Act - var endpoints = dataSource.Endpoints; - - // Assert - var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); - AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); - } - private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 6430ff2005..464a36beb5 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -72,6 +72,30 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public string[] Routers { get; set; } } + [Fact] + public async Task DataTokens_ReturnsDataTokensForRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/DataTokensRoute/DataTokens/Index"); + + // Assert + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + Assert.Single(result, kvp => kvp.Key == "hasDataTokens" && ((bool)kvp.Value) == true); + } + + [Fact] + public async Task DataTokens_ReturnsNoDataTokensForRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/DataTokens/Index"); + + // Assert + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(body); + Assert.Empty(result); + } + [Fact] public virtual async Task ConventionalRoutedController_ActionIsReachable() { diff --git a/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs b/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs new file mode 100644 index 0000000000..b6ae438777 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs @@ -0,0 +1,15 @@ +// 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; + +namespace RoutingWebSite +{ + public class DataTokensController : Controller + { + public object Index() + { + return RouteData.DataTokens; + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 984e4cc5c2..cea64f826f 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -24,12 +24,19 @@ namespace RoutingWebSite { app.UseMvc(routes => { + routes.MapRoute( + "DataTokensRoute", + "DataTokensRoute/{controller}/{action}", + defaults: null, + constraints: new { controller = "DataTokens" }, + dataTokens: new { hasDataTokens = true }); + routes.MapAreaRoute( - "flightRoute", - "adminRoute", - "{area:exists}/{controller}/{action}", - new { controller = "Home", action = "Index" }, - new { area = "Travel" }); + "flightRoute", + "adminRoute", + "{area:exists}/{controller}/{action}", + defaults: new { controller = "Home", action = "Index" }, + constraints: new { area = "Travel" }); routes.MapRoute( "ActionAsMethod", diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 33c10a0275..1b34b2cfb0 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -24,12 +24,19 @@ namespace RoutingWebSite { app.UseMvc(routes => { + routes.MapRoute( + "DataTokensRoute", + "DataTokensRoute/{controller}/{action}", + defaults: null, + constraints: new { controller = "DataTokens" }, + dataTokens: new { hasDataTokens = true }); + routes.MapAreaRoute( - "flightRoute", - "adminRoute", - "{area:exists}/{controller}/{action}", - new { controller = "Home", action = "Index" }, - new { area = "Travel" }); + "flightRoute", + "adminRoute", + "{area:exists}/{controller}/{action}", + defaults: new { controller = "Home", action = "Index" }, + constraints: new { area = "Travel" }); routes.MapRoute( "ActionAsMethod", From f90a47c5aff7c805b8102e3d07c71687afc6c061 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 22 Aug 2018 13:17:42 -0700 Subject: [PATCH 210/316] Introduce ProducesErrorResponseTypeAttribute Fixes https://github.com/aspnet/Mvc/issues/8288 --- .../ApiResponseTypeProvider.cs | 73 +++--- .../ApiBehaviorApplicationModelProvider.cs | 22 ++ .../ProducesErrorResponseTypeAttribute.cs | 38 +++ .../ApiResponseTypeProviderTest.cs | 241 ++++++++++++++++++ ...ApiBehaviorApplicationModelProviderTest.cs | 167 +++++++++++- .../ApiExplorerTest.cs | 46 ++-- 6 files changed, 531 insertions(+), 56 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index ec791aaee0..d2eec34199 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -46,7 +46,13 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer responseMetadataAttributes = apiConventionResult.ResponseMetadataProviders; } - var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType); + var defaultErrorType = typeof(void); + if (action.Properties.TryGetValue(typeof(ProducesErrorResponseTypeAttribute), out result)) + { + defaultErrorType = ((ProducesErrorResponseTypeAttribute)result).Type; + } + + var apiResponseTypes = GetApiResponseTypes(responseMetadataAttributes, runtimeReturnType, defaultErrorType); return apiResponseTypes; } @@ -69,7 +75,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer private ICollection GetApiResponseTypes( IReadOnlyList responseMetadataAttributes, - Type type) + Type type, + Type defaultErrorType) { var results = new Dictionary(); @@ -83,48 +90,39 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { metadataAttribute.SetContentTypes(contentTypes); - ApiResponseType apiResponseType; + var statusCode = metadataAttribute.StatusCode; - if (metadataAttribute is IApiDefaultResponseMetadataProvider) + var apiResponseType = new ApiResponseType { - apiResponseType = new ApiResponseType + Type = metadataAttribute.Type, + StatusCode = statusCode, + IsDefaultResponse = metadataAttribute is IApiDefaultResponseMetadataProvider, + }; + + if (apiResponseType.Type == typeof(void)) + { + if (type != null && (statusCode == StatusCodes.Status200OK || statusCode == StatusCodes.Status201Created)) { - IsDefaultResponse = true, - Type = metadataAttribute.Type, - }; - } - else if (metadataAttribute.Type == typeof(void) && - type != null && - (metadataAttribute.StatusCode == StatusCodes.Status200OK || metadataAttribute.StatusCode == StatusCodes.Status201Created)) - { - // ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified. - // In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a - // [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred - // from the return type. - apiResponseType = new ApiResponseType + // ProducesResponseTypeAttribute's constructor defaults to setting "Type" to void when no value is specified. + // In this event, use the action's return type for 200 or 201 status codes. This lets you decorate an action with a + // [ProducesResponseType(201)] instead of [ProducesResponseType(201, typeof(Person)] when typeof(Person) can be inferred + // from the return type. + apiResponseType.Type = type; + } + else if (IsClientError(statusCode) || apiResponseType.IsDefaultResponse) { - StatusCode = metadataAttribute.StatusCode, - Type = type, - }; - } - else if (metadataAttribute.Type != null) - { - apiResponseType = new ApiResponseType - { - StatusCode = metadataAttribute.StatusCode, - Type = metadataAttribute.Type, - }; - } - else - { - continue; + // Use the default error type for "default" responses or 4xx client errors if no response type is specified. + apiResponseType.Type = defaultErrorType; + } } - results[apiResponseType.StatusCode] = apiResponseType; + if (apiResponseType.Type != null) + { + results[apiResponseType.StatusCode] = apiResponseType; + } } } - // Set the default status only when no status has already been set explicitly if (results.Count == 0 && type != null) { @@ -225,5 +223,10 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return declaredReturnType; } + + private static bool IsClientError(int statusCode) + { + return statusCode >= 400 && statusCode < 500; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs index 5b21f55378..9eeb83769a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class ApiBehaviorApplicationModelProvider : IApplicationModelProvider { + private readonly ProducesErrorResponseTypeAttribute DefaultErrorType = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails)); private readonly ApiBehaviorOptions _apiBehaviorOptions; private readonly IModelMetadataProvider _modelMetadataProvider; private readonly ModelStateInvalidFilter _modelStateInvalidFilter; @@ -105,6 +106,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal AddMultipartFormDataConsumesAttribute(actionModel); DiscoverApiConvention(actionModel, conventions); + + DiscoverErrorResponseType(actionModel); } } } @@ -274,6 +277,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + internal void DiscoverErrorResponseType(ActionModel actionModel) + { + var errorTypeAttribute = + actionModel.Attributes.OfType().FirstOrDefault() ?? + actionModel.Controller.Attributes.OfType().FirstOrDefault() ?? + actionModel.Controller.ControllerType.Assembly.GetCustomAttribute(); + + if (!_apiBehaviorOptions.SuppressMapClientErrors) + { + // If ClientErrorFactory is being used and the application does not supply a error response type, assume ProblemDetails. + errorTypeAttribute = errorTypeAttribute ?? DefaultErrorType; + } + + if (errorTypeAttribute != null) + { + actionModel.Properties[typeof(ProducesErrorResponseTypeAttribute)] = errorTypeAttribute; + } + } + private bool ParameterExistsInAnyRoute(ActionModel actionModel, string parameterName) { foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(actionModel)) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs new file mode 100644 index 0000000000..f74a826e93 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// Specifies the type returned by default by controllers annotated with . + /// + /// specifies the error model type associated with a + /// for a client error (HTTP Status Code 4xx) when no value is provided. When no value is specified, MVC assumes the + /// client error type to be , if mapping client errors () + /// is used. + /// + /// + /// Use this to configure the default error type if your application uses a custom error type to respond. + /// + /// + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)] + public sealed class ProducesErrorResponseTypeAttribute : Attribute + { + /// + /// Initializes a new instance of . + /// + /// The error type. + public ProducesErrorResponseTypeAttribute(Type type) + { + Type = type ?? throw new ArgumentNullException(nameof(type)); + } + + /// + /// Gets the default error type. + /// + public Type Type { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 11c9bcb8e0..aee457d5a6 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -309,6 +309,247 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } + [Fact] + public void GetApiResponseTypes_UsesErrorType_ForClientErrors() + { + // Arrange + var errorType = typeof(InvalidTimeZoneException); + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(404), + new ProducesResponseTypeAttribute(415), + }); + + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(404, responseType.StatusCode); + Assert.Equal(errorType, responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(415, responseType.StatusCode); + Assert.Equal(errorType, responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [Fact] + public void GetApiResponseTypes_UsesErrorType_ForDefaultResponse() + { + // Arrange + var errorType = typeof(ProblemDetails); + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesDefaultResponseTypeAttribute(), + }); + + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(errorType, responseType.Type); + Assert.True(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [Fact] + public void GetApiResponseTypes_DoesNotUseErrorType_IfSpecified() + { + // Arrange + var errorType = typeof(InvalidTimeZoneException); + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(typeof(DivideByZeroException), 415), + new ProducesDefaultResponseTypeAttribute(typeof(DivideByZeroException)), + }); + + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(errorType); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(DivideByZeroException), responseType.Type); + Assert.True(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(415, responseType.StatusCode); + Assert.Equal(typeof(DivideByZeroException), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [Fact] + public void GetApiResponseTypes_DoesNotUseErrorType_ForNonClientErrors() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(201), + new ProducesResponseTypeAttribute(300), + new ProducesResponseTypeAttribute(500), + }); + + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(InvalidTimeZoneException)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(201, responseType.StatusCode); + Assert.Equal(typeof(BaseModel), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(300, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(500, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + + [Fact] + public void GetApiResponseTypes_AllowsUsingVoid() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor( + typeof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController), + nameof(GetApiResponseTypes_ReturnsResponseTypesFromDefaultConventionsController.DeleteBase)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(typeof(InvalidCastException), 400), + new ProducesResponseTypeAttribute(415), + new ProducesDefaultResponseTypeAttribute(), + }); + + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(void)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.True(responseType.IsDefaultResponse); + Assert.Equal(typeof(void), responseType.Type); + Assert.Empty(responseType.ApiResponseFormats); + }, + responseType => + { + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(typeof(InvalidCastException), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(415, responseType.StatusCode); + Assert.Equal(typeof(void), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Empty(responseType.ApiResponseFormats); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index c0c415573f..c4104b9027 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -12,7 +12,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ApiExplorer; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.Logging.Abstractions; @@ -20,6 +19,8 @@ using Microsoft.Extensions.Options; using Moq; using Xunit; +[assembly: Microsoft.AspNetCore.Mvc.ProducesErrorResponseType(typeof(InvalidEnumArgumentException))] + namespace Microsoft.AspNetCore.Mvc.Internal { public class ApiBehaviorApplicationModelProviderTest @@ -1042,9 +1043,6 @@ Environment.NewLine + "int b"; var actionModel = new ActionModel( typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), Array.Empty()); - actionModel.Filters.Add(new AuthorizeFilter()); - actionModel.Filters.Add(new ServiceFilterAttribute(typeof(object))); - actionModel.Filters.Add(new ConsumesAttribute("application/xml")); var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; // Act @@ -1060,6 +1058,167 @@ Environment.NewLine + "int b"; }); } + [Fact] + public void DiscoverErrorResponseType_SetsProblemDetails_IfActionHasNoAttributes() + { + // Arrange + var expected = typeof(ProblemDetails); + var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() }); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()) + { + Controller = controllerModel, + }; + var provider = GetProvider(); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); + var value = Assert.IsType(kvp.Value); + Assert.Equal(expected, value.Type); + }); + } + + [Fact] + public void DiscoverErrorResponseType_DoesNotSetDefaultProblemDetailsResponse_IfSuppressMapClientErrorsIsSet() + { + // Arrange + var expected = typeof(ProblemDetails); + var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() }); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()) + { + Controller = controllerModel, + }; + var provider = GetProvider(new ApiBehaviorOptions + { + InvalidModelStateResponseFactory = _ => null, + SuppressMapClientErrors = true, + }); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Empty(actionModel.Properties); + } + + [Fact] + public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAsssembly() + { + // Arrange + var expected = typeof(InvalidEnumArgumentException); + var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new object() }); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()) + { + Controller = controllerModel, + }; + var provider = GetProvider(); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); + var value = Assert.IsType(kvp.Value); + Assert.Equal(expected, value.Type); + }); + } + + [Fact] + public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnController() + { + // Arrange + var expected = typeof(InvalidTimeZoneException); + var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(expected) }); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + Array.Empty()) + { + Controller = controllerModel, + }; + var provider = GetProvider(); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); + var value = Assert.IsType(kvp.Value); + Assert.Equal(expected, value.Type); + }); + } + + [Fact] + public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnAction() + { + // Arrange + var expected = typeof(InvalidTimeZoneException); + var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(typeof(Guid)) }); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + new[] { new ProducesErrorResponseTypeAttribute(expected) }) + { + Controller = controllerModel, + }; + var provider = GetProvider(); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); + var value = Assert.IsType(kvp.Value); + Assert.Equal(expected, value.Type); + }); + } + + [Fact] + public void DiscoverErrorResponseType_AllowsVoidsType() + { + // Arrange + var expected = typeof(void); + var actionModel = new ActionModel( + typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), + new[] { new ProducesErrorResponseTypeAttribute(expected) }); + var provider = GetProvider(); + + // Act + provider.DiscoverErrorResponseType(actionModel); + + // Assert + Assert.Collection( + actionModel.Properties, + kvp => + { + Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); + var value = Assert.IsType(kvp.Value); + Assert.Equal(expected, value.Type); + }); + } + [Fact] public void OnProvidersExecuting_AddsClientErrorResultFilter() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index d216ad4aff..6230542cb0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1180,9 +1180,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(404, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } @@ -1215,7 +1215,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ApiConvention_ForMethodWithResponseTypeAttributes() { // Arrange - var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + var expectedMediaTypes = new[] { "application/json" }; // Act var response = await Client.PostAsync( @@ -1236,15 +1236,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(403, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } [Fact] public async Task ApiConvention_ForPostMethodThatMatchesConvention() { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + // Act var response = await Client.PostAsync( $"ApiExplorerResponseTypeWithApiConventionController/PostTaskOfProduct", @@ -1268,15 +1271,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(400, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } [Fact] public async Task ApiConvention_ForPutActionThatMatchesConvention() { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + // Act var response = await Client.PutAsync( $"ApiExplorerResponseTypeWithApiConventionController/Put", @@ -1300,21 +1306,24 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(400, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(404, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } [Fact] public async Task ApiConvention_ForDeleteActionThatMatchesConvention() { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + // Act var response = await Client.DeleteAsync( $"ApiExplorerResponseTypeWithApiConventionController/DeleteProductAsync"); @@ -1337,21 +1346,24 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(400, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(404, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } [Fact] public async Task ApiConvention_ForActionWtihApiConventionMethod() { + // Arrange + var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; + // Act var response = await Client.PostAsync( "ApiExplorerResponseTypeWithApiConventionController/PostItem", @@ -1371,9 +1383,9 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }, responseType => { - Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); Assert.Equal(409, responseType.StatusCode); - Assert.Empty(responseType.ResponseFormats); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); }); } From 3dfa26f7e364fcd913e972a1188579c808d04d52 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Tue, 28 Aug 2018 03:08:55 +0100 Subject: [PATCH 211/316] Resolve virtual ViewContext max once per method --- .../RazorPage.cs | 11 +++--- .../RazorPageBase.cs | 39 +++++++++++-------- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs index 3d13f977f7..53fd192c24 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs @@ -196,11 +196,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor else if (required) { // If the section is not found, and it is not optional, throw an error. - var message = Resources.FormatSectionNotDefined( - ViewContext.ExecutingFilePath, - sectionName, - ViewContext.View.Path); - throw new InvalidOperationException(message); + var viewContext = ViewContext; + throw new InvalidOperationException( + Resources.FormatSectionNotDefined( + viewContext.ExecutingFilePath, + sectionName, + viewContext.View.Path)); } else { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs index 96ef957f60..016076520f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs @@ -51,13 +51,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor { get { - if (ViewContext == null) + var viewContext = ViewContext; + if (viewContext == null) { - var message = Resources.FormatViewContextMustBeSet("ViewContext", "Output"); - throw new InvalidOperationException(message); + throw new InvalidOperationException(Resources.FormatViewContextMustBeSet(nameof(ViewContext), nameof(Output))); } - return ViewContext.Writer; + return viewContext.Writer; } } @@ -183,8 +183,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// public void StartTagHelperWritingScope(HtmlEncoder encoder) { + var viewContext = ViewContext; var buffer = new ViewBuffer(BufferScope, Path, ViewBuffer.TagHelperPageSize); - TagHelperScopes.Push(new TagHelperScopeInfo(buffer, HtmlEncoder, ViewContext.Writer)); + TagHelperScopes.Push(new TagHelperScopeInfo(buffer, HtmlEncoder, viewContext.Writer)); // If passed an HtmlEncoder, override the property. if (encoder != null) @@ -194,7 +195,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor // We need to replace the ViewContext's Writer to ensure that all content (including content written // from HTML helpers) is redirected. - ViewContext.Writer = new ViewBufferTextWriter(buffer, ViewContext.Writer.Encoding); + viewContext.Writer = new ViewBufferTextWriter(buffer, viewContext.Writer.Encoding); } /// @@ -238,7 +239,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor throw new InvalidOperationException(Resources.RazorPage_NestingAttributeWritingScopesNotSupported); } - _pageWriter = ViewContext.Writer; + var viewContext = ViewContext; + _pageWriter = viewContext.Writer; if (_valueBuffer == null) { @@ -247,7 +249,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor // We need to replace the ViewContext's Writer to ensure that all content (including content written // from HTML helpers) is redirected. - ViewContext.Writer = _valueBuffer; + viewContext.Writer = _valueBuffer; } @@ -284,15 +286,18 @@ namespace Microsoft.AspNetCore.Mvc.Razor throw new ArgumentNullException(nameof(writer)); } - _textWriterStack.Push(ViewContext.Writer); - ViewContext.Writer = writer; + var viewContext = ViewContext; + _textWriterStack.Push(viewContext.Writer); + viewContext.Writer = writer; } // Internal for unit testing. protected internal virtual TextWriter PopWriter() { - ViewContext.Writer = _textWriterStack.Pop(); - return ViewContext.Writer; + var viewContext = ViewContext; + var writer = _textWriterStack.Pop(); + viewContext.Writer = writer; + return writer; } public virtual string Href(string contentPath) @@ -304,9 +309,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor if (_urlHelper == null) { - var services = ViewContext?.HttpContext.RequestServices; + var viewContext = ViewContext; + var services = viewContext?.HttpContext.RequestServices; var factory = services.GetRequiredService(); - _urlHelper = factory.GetUrlHelper(ViewContext); + _urlHelper = factory.GetUrlHelper(viewContext); } return _urlHelper.Content(contentPath); @@ -637,8 +643,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// before flushes the headers. public virtual HtmlString SetAntiforgeryCookieAndHeader() { - var antiforgery = ViewContext?.HttpContext.RequestServices.GetRequiredService(); - antiforgery.SetCookieTokenAndHeader(ViewContext?.HttpContext); + var viewContext = ViewContext; + var antiforgery = viewContext?.HttpContext.RequestServices.GetRequiredService(); + antiforgery.SetCookieTokenAndHeader(viewContext?.HttpContext); return HtmlString.Empty; } From 1128bd572cf46e782a71d9c4240b2c737d8b0a72 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 30 Aug 2018 13:08:07 -0700 Subject: [PATCH 212/316] Add a functional test for middleware after routing It came up during routing discussions that we don't have any tests for this scenario. --- .../RoutingTestsBase.cs | 13 +++++++++++++ test/WebSites/RoutingWebSite/Startup.cs | 6 ++++++ test/WebSites/RoutingWebSite/StartupWith21Compat.cs | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 464a36beb5..175dfaef1a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -1265,6 +1265,19 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(actionName, result.Action); } + [Fact] + public async Task CanRunMiddlewareAfterRouting() + { + // Act + var response = await Client.GetAsync("/afterrouting"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello from middleware after routing", content); + } + + protected static LinkBuilder LinkFrom(string url) { return new LinkBuilder(url); diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index cea64f826f..08a67d963f 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -47,6 +48,11 @@ namespace RoutingWebSite "RouteWithOptionalSegment", "{controller}/{action}/{path?}"); }); + + app.Map("/afterrouting", b => b.Run(c => + { + return c.Response.WriteAsync("Hello from middleware after routing"); + })); } } } \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 1b34b2cfb0..eb891a53c5 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -47,6 +48,11 @@ namespace RoutingWebSite "RouteWithOptionalSegment", "{controller}/{action}/{path?}"); }); + + app.Map("/afterrouting", b => b.Run(c => + { + return c.Response.WriteAsync("Hello from middleware after routing"); + })); } } } \ No newline at end of file From d8b7dbd1f3fef9789b12169de2bb25481ebad05c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 30 Aug 2018 15:17:09 -0700 Subject: [PATCH 213/316] Avoid null refs when IStatusCodeActionResult cannot be discovered --- .../ApiControllerSymbolCache.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs index e92c0927ca..7cbec7eb36 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers StatusCodeValueAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.StatusCodeValueAttribute); var statusCodeActionResult = compilation.GetTypeByMetadataName(ApiSymbolNames.IStatusCodeActionResult); - StatusCodeActionResultStatusProperty = (IPropertySymbol)statusCodeActionResult.GetMembers("StatusCode")[0]; + StatusCodeActionResultStatusProperty = (IPropertySymbol)statusCodeActionResult?.GetMembers("StatusCode")[0]; var disposable = compilation.GetSpecialType(SpecialType.System_IDisposable); var members = disposable.GetMembers(nameof(IDisposable.Dispose)); From b9793f0a1d9a75940c50e31e956d09397a5d1876 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 2 Sep 2018 12:21:18 -0700 Subject: [PATCH 214/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d718620eed..712c6521e9 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview2-35090 - 2.2.0-preview1-20180807.2 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-a-preview2-routeconstraint-httpcontext-16912 - 2.2.0-a-preview2-routeconstraint-httpcontext-16912 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 + 2.2.0-preview2-35143 + 2.2.0-preview1-20180821.1 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 5.2.6 2.8.0 2.8.0 - 2.2.0-preview2-35090 + 2.2.0-preview2-35143 1.7.0 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 2.1.0 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 2.0.9 2.1.2 2.2.0-preview1-26618-02 - 2.2.0-preview2-35090 - 2.2.0-preview2-35090 + 2.2.0-preview2-35143 + 2.2.0-preview2-35143 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 3fbcc80189..ad704918df 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180807.2 -commithash:11495dbd236104434e08cb1152fcb58cf2a20923 +version:2.2.0-preview1-20180821.1 +commithash:c8d0cc52cd1abb697be24e288ffd54f8fae8bf17 From 6498c89f880d65681e61e1bdd86c755fb5136f7e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Wed, 5 Sep 2018 16:28:46 -0700 Subject: [PATCH 215/316] Update branding to 2.2.0-preview3 --- version.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.props b/version.props index d623e08663..e01d0a1143 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@  2.2.0 - preview2 + preview3 t000 a- @@ -11,7 +11,7 @@ $(VersionSuffix)-$(BuildNumber) 0.2.0 - preview2 + preview3 $(ExperimentalVersionPrefix) $(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final From bca31601905a100896cf1a460ec062df023862f5 Mon Sep 17 00:00:00 2001 From: Hassan Hashemi Date: Tue, 4 Sep 2018 11:52:44 +0430 Subject: [PATCH 216/316] Remove redundant check fixed https://github.com/aspnet/Mvc/issues/8374 --- .../ActionContext.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs index f7735c1637..744a439dc7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs @@ -31,15 +31,11 @@ namespace Microsoft.AspNetCore.Mvc /// The to copy. public ActionContext(ActionContext actionContext) : this( - actionContext.HttpContext, - actionContext.RouteData, - actionContext.ActionDescriptor, - actionContext.ModelState) + actionContext?.HttpContext, + actionContext?.RouteData, + actionContext?.ActionDescriptor, + actionContext?.ModelState) { - if (actionContext == null) - { - throw new ArgumentNullException(nameof(actionContext)); - } } /// @@ -136,4 +132,4 @@ namespace Microsoft.AspNetCore.Mvc get; set; } } -} \ No newline at end of file +} From b48b282ad86800378a3ac29f898779ae63e4223a Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Thu, 6 Sep 2018 01:07:04 +0100 Subject: [PATCH 217/316] Add RazorRendering benchmarkapp (#8366) * Add RazorRendering benchmarkapp --- Mvc.sln | 17 ++- benchmarkapps/RazorRendering/Data/DataA.cs | 26 ++++ benchmarkapps/RazorRendering/Data/DataB.cs | 25 ++++ .../Pages/Category/PageA.cshtml | 138 ++++++++++++++++++ .../Pages/Category/PageA.cshtml.cs | 35 +++++ .../Pages/Category/_Subcategories.cshtml | 12 ++ benchmarkapps/RazorRendering/Pages/Page.cs | 26 ++++ .../Pages/Shared/_Layout.cshtml | 31 ++++ .../RazorRendering/Pages/_ViewImports.cshtml | 2 + .../RazorRendering/Pages/_ViewStart.cshtml | 3 + .../RazorRendering/RazorRendering.csproj | 23 +++ benchmarkapps/RazorRendering/Readme.md | 1 + benchmarkapps/RazorRendering/Startup.cs | 78 ++++++++++ 13 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 benchmarkapps/RazorRendering/Data/DataA.cs create mode 100644 benchmarkapps/RazorRendering/Data/DataB.cs create mode 100644 benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml create mode 100644 benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs create mode 100644 benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml create mode 100644 benchmarkapps/RazorRendering/Pages/Page.cs create mode 100644 benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml create mode 100644 benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml create mode 100644 benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml create mode 100644 benchmarkapps/RazorRendering/RazorRendering.csproj create mode 100644 benchmarkapps/RazorRendering/Readme.md create mode 100644 benchmarkapps/RazorRendering/Startup.cs diff --git a/Mvc.sln b/Mvc.sln index 7478c7b22e..2bcf1d1580 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -178,7 +178,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BasicViews", "benchmarkapps EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Api.Analyzers", "src\Microsoft.AspNetCore.Mvc.Api.Analyzers\Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj", "{3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorRendering", "benchmarkapps\RazorRendering\RazorRendering.csproj", "{D7C6A696-F232-4288-BCCD-367407E4A934}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -952,6 +954,18 @@ Global {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|Mixed Platforms.Build.0 = Release|Any CPU {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.ActiveCfg = Release|Any CPU {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA}.Release|x86.Build.0 = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|x86.ActiveCfg = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Debug|x86.Build.0 = Debug|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Any CPU.Build.0 = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.ActiveCfg = Release|Any CPU + {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1025,6 +1039,7 @@ Global {E89EB74D-C1CE-456F-B42D-CCF1575E0CFB} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/benchmarkapps/RazorRendering/Data/DataA.cs b/benchmarkapps/RazorRendering/Data/DataA.cs new file mode 100644 index 0000000000..42cbf300ee --- /dev/null +++ b/benchmarkapps/RazorRendering/Data/DataA.cs @@ -0,0 +1,26 @@ +using Microsoft.AspNetCore.Html; + +namespace Data +{ + public class DataA + { + public DataA(int id, HtmlString icon, HtmlString html, string name, int seconds, int max, float perHour) + { + Id = id; + Icon = icon; + Html = html; + Name = name; + Seconds = seconds; + Max = max; + PerHour = perHour; + } + + public int Id { get; } + public HtmlString Icon { get; } + public HtmlString Html { get; } + public string Name { get; } + public int Seconds { get; } + public int Max { get; } + public float PerHour { get; } + } +} diff --git a/benchmarkapps/RazorRendering/Data/DataB.cs b/benchmarkapps/RazorRendering/Data/DataB.cs new file mode 100644 index 0000000000..be2830e67f --- /dev/null +++ b/benchmarkapps/RazorRendering/Data/DataB.cs @@ -0,0 +1,25 @@ +using System; +using Microsoft.AspNetCore.Html; + +namespace Data +{ + public class DataB + { + public DataB(int id, HtmlString icon, string name, int value, DateTimeOffset startDate, DateTimeOffset completeDate) + { + Id = id; + Icon = icon; + Name = name; + Value = value; + StartDate = startDate; + CompleteDate = completeDate; + } + + public int Id { get; } + public HtmlString Icon { get; } + public string Name { get; } + public int Value { get; } + public DateTimeOffset StartDate { get; } + public DateTimeOffset CompleteDate { get; } + } +} diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml new file mode 100644 index 0000000000..036af8cb1f --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml @@ -0,0 +1,138 @@ +@page +@model Pages.PageA +@using static System.Convert + +@section Subcategories { + +} + +@section Tabs { + Sub cat A + Sub cat B + Sub cat C + Sub cat D +} + +@switch (Model.Value) +{ + case 0: +

@Model.Name Type A

+ break; + case 1: +

@Model.Name Type B

+ break; + case 2: +

@Model.Name Type C

+ break; +} + + +

Something Something

+
+ + + + + + @if (Model.Value3 != 0) + { + + } + + + @{ + foreach (var data in Model.Data1) + { + + + + + @if (Model.Value3 != 0) + { + + } + + + } + } +
SomethingSomething SomethingSomething
@data.Icon@data.Name@data.Html + @(new TimeSpan(0, 0, (int)data.Seconds))
+ (@data.PerHour.ToString("N2") p/h) +
+
+ + + + max + +
+
+
+ +

Something something something

+
+@{ + if (Model.Data2.Count > 0) + { + + + + + + + + + + + + + @foreach (var data in Model.Data2) + { + var StartDate = data.StartDate; + var CompleteDate = data.CompleteDate; + + + + + + + @{ + float percentage = 100f; + + var totalTime = CompleteDate.Subtract(StartDate).TotalMilliseconds; + if (totalTime > 1000) + { + percentage = 100f * (float)DateTimeOffset.UtcNow.Subtract(StartDate).TotalMilliseconds / (float)totalTime; + } + + percentage = MathF.Min(100f, MathF.Max(0f, percentage)); + + var startDate = ToInt64(StartDate.Subtract(DateTime.UnixEpoch).Ticks / (double)10000); + var endDate = ToInt64(CompleteDate.Subtract(DateTime.UnixEpoch).Ticks / (double)10000); + } + + + + + + } +
SomethingASomethingBSomethingCSomethingDSomethingESomethingF
@data.Icon@data.Name@data.Value@StartDate.ToString("dd MMM HH:mm:ss") +
+
+
+
@CompleteDate.ToString("dd MMM HH:mm:ss") + @(CompleteDate.Subtract(DateTime.UtcNow).ToString()) + +
+ + +
+
+ } + else + { + Something @Model.Name something something something something. + } + +} +
\ No newline at end of file diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs new file mode 100644 index 0000000000..8f2d98dc78 --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs @@ -0,0 +1,35 @@ +using Data; +using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Pages +{ + public class PageA : Page + { + public int Value { get; } = 0; + public int Value1 { get; } = 0; + public int Value2 { get; } = 0; + public int Value3 { get; } = 1; + public bool Condition { get; } = true; + + public string Name { get; } = "A Name"; + + public List Data1 { get; } + public List Data2 { get; } + + public PageA(List dataA, List dataB, ILogger logger) : base(logger) + { + Data1 = dataA; + Data2 = dataB; + } + + public async Task OnGetAsync() + { + PageTitle = "PageA Title"; + PageIcon = "sicon dialogue_pagea"; + await Task.Delay(0); + } + } + +} \ No newline at end of file diff --git a/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml b/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml new file mode 100644 index 0000000000..3681e4e9cd --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml @@ -0,0 +1,12 @@ +@model Pages.Page + +
+
+
+
+
+
+
+
+
+
diff --git a/benchmarkapps/RazorRendering/Pages/Page.cs b/benchmarkapps/RazorRendering/Pages/Page.cs new file mode 100644 index 0000000000..e02d03b5de --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/Page.cs @@ -0,0 +1,26 @@ +using System.Text.Encodings.Web; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.Extensions.Logging; + +namespace Pages +{ + public class Page : PageModel + { + public ILogger Logger { get; } + + public string PageIcon { get; protected set; } + public string PageTitle { get; protected set; } + public string PageUrl { get; protected set; } + + public Page(ILogger logger) + { + Logger = logger; + } + + + public void AddErrorMessage(string message) + { + Response.Headers.Add("X-Error-Message", UrlEncoder.Default.Encode(message)); + } + } +} \ No newline at end of file diff --git a/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml b/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml new file mode 100644 index 0000000000..6e3fbe8720 --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml @@ -0,0 +1,31 @@ +@model Pages.Page + +@RenderSection("Subcategories") +
+
@Model.PageTitle
+@{ + var hasTabs = IsSectionDefined("Tabs"); +} +
+ @if (hasTabs) + { +
+ @RenderSection("Tabs", required: false) +
+ } +
+
+
+
+
+ @RenderBody() +
+
+
+
+
+
+
+
+
+
diff --git a/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml b/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml new file mode 100644 index 0000000000..b0ad3a90b5 --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml @@ -0,0 +1,2 @@ + +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers \ No newline at end of file diff --git a/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml b/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml new file mode 100644 index 0000000000..d641c67f33 --- /dev/null +++ b/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} \ No newline at end of file diff --git a/benchmarkapps/RazorRendering/RazorRendering.csproj b/benchmarkapps/RazorRendering/RazorRendering.csproj new file mode 100644 index 0000000000..dbbbfc0027 --- /dev/null +++ b/benchmarkapps/RazorRendering/RazorRendering.csproj @@ -0,0 +1,23 @@ + + + netcoreapp2.2 + + + + + + + + + + + + + + + + + + diff --git a/benchmarkapps/RazorRendering/Readme.md b/benchmarkapps/RazorRendering/Readme.md new file mode 100644 index 0000000000..15640cadc8 --- /dev/null +++ b/benchmarkapps/RazorRendering/Readme.md @@ -0,0 +1 @@ +Url: /Category/PageA \ No newline at end of file diff --git a/benchmarkapps/RazorRendering/Startup.cs b/benchmarkapps/RazorRendering/Startup.cs new file mode 100644 index 0000000000..3092f1639c --- /dev/null +++ b/benchmarkapps/RazorRendering/Startup.cs @@ -0,0 +1,78 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using Data; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.Html; +using Microsoft.Extensions.Configuration; +using System.IO; + +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + services.AddScoped>(_ => DataA); + services.AddScoped>(_ => DataB); + services.AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); + } + + public void Configure(IApplicationBuilder app, IHostingEnvironment env) + { + app.UseMvc(); + } + + private static List DataA = GenerateDataA(); + + private static List GenerateDataA() + { + var dataA = new List(); + + foreach (var i in Enumerable.Range(0, 100)) + { + dataA.Add(new DataA(i, new HtmlString(i.ToString()), new HtmlString(i.ToString()), i.ToString(), i, i, 60f / i)); + } + + return dataA; + } + + private static List DataB = GenerateDataB(); + + private static List GenerateDataB() + { + var utc = DateTimeOffset.UtcNow; + var dataB = new List(); + foreach (var i in Enumerable.Range(0, 100)) + { + dataB.Add(new DataB(i, new HtmlString(i.ToString()), i.ToString(), i, utc, utc)); + } + + return dataB; + } + + public static void Main(string[] args) + { + var host = CreateWebHostBuilder(args) + .Build(); + + host.Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) + { + var configuration = new ConfigurationBuilder() + .AddEnvironmentVariables() + .AddCommandLine(args) + .Build(); + + return new WebHostBuilder() + .UseKestrel() + .UseUrls("http://+:5000") + .UseConfiguration(configuration) + .UseContentRoot(Directory.GetCurrentDirectory()) + .UseStartup(); + } +} \ No newline at end of file From fabe189470ce7a4d116421607acf67d5088f81bf Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Tue, 4 Sep 2018 20:56:56 -0700 Subject: [PATCH 218/316] React to LinkGenerator changes --- build/dependencies.props | 4 +- .../Routing/EndpointRoutingUrlHelper.cs | 48 ++++++-------- .../Routing/UrlHelperBase.cs | 62 +++++++++++++++++++ .../Internal/MvcEndpointDataSourceTests.cs | 6 +- 4 files changed, 85 insertions(+), 35 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 712c6521e9..4de4f3a0fd 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview2-35143 2.2.0-preview2-35143 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-a-preview3-link-generator-16951 + 2.2.0-a-preview3-link-generator-16951 2.2.0-preview2-35143 2.2.0-preview2-35143 2.2.0-preview2-35143 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs index 8ddd49c2f1..adc94a7d8e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.Logging; @@ -51,46 +52,41 @@ namespace Microsoft.AspNetCore.Mvc.Routing throw new ArgumentNullException(nameof(urlActionContext)); } - var valuesDictionary = GetValuesDictionary(urlActionContext.Values); + var values = GetValuesDictionary(urlActionContext.Values); if (urlActionContext.Action == null) { - if (!valuesDictionary.ContainsKey("action") && + if (!values.ContainsKey("action") && AmbientValues.TryGetValue("action", out var action)) { - valuesDictionary["action"] = action; + values["action"] = action; } } else { - valuesDictionary["action"] = urlActionContext.Action; + values["action"] = urlActionContext.Action; } if (urlActionContext.Controller == null) { - if (!valuesDictionary.ContainsKey("controller") && + if (!values.ContainsKey("controller") && AmbientValues.TryGetValue("controller", out var controller)) { - valuesDictionary["controller"] = controller; + values["controller"] = controller; } } else { - valuesDictionary["controller"] = urlActionContext.Controller; + values["controller"] = urlActionContext.Controller; } - var successfullyGeneratedLink = _linkGenerator.TryGetLink( + + var path = _linkGenerator.GetPathByRouteValues( ActionContext.HttpContext, - valuesDictionary, - out var link); - if (!successfullyGeneratedLink) - { - //TODO: log here - - return null; - } - - return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, link, urlActionContext.Fragment); + routeName: null, + values, + new FragmentString(urlActionContext.Fragment == null ? null : "#" + urlActionContext.Fragment)); + return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, path); } /// @@ -101,20 +97,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing throw new ArgumentNullException(nameof(routeContext)); } - var valuesDictionary = routeContext.Values as RouteValueDictionary ?? GetValuesDictionary(routeContext.Values); - - var successfullyGeneratedLink = _linkGenerator.TryGetLink( + var path = _linkGenerator.GetPathByRouteValues( ActionContext.HttpContext, routeContext.RouteName, - valuesDictionary, - out var link); - - if (!successfullyGeneratedLink) - { - return null; - } - - return GenerateUrl(routeContext.Protocol, routeContext.Host, link, routeContext.Fragment); + routeContext.Values, + new FragmentString(routeContext.Fragment == null ? null : "#" + routeContext.Fragment)); + return GenerateUrl(routeContext.Protocol, routeContext.Host, path); } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs index 730323f33b..a68c9c988b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs @@ -201,6 +201,68 @@ namespace Microsoft.AspNetCore.Mvc.Routing } } + /// + /// Generates a URI from the provided components. + /// + /// The URI scheme/protocol. + /// The URI host. + /// The URI path and remaining portions (path, query, and fragment). + /// + /// An absolute URI if the or is specified, otherwise generates a + /// URI with an absolute path. + /// + protected string GenerateUrl(string protocol, string host, string path) + { + // This method is similar to GenerateUrl, but it's used for EndpointRouting. It ignores pathbase and fragment + // because those have already been incorporated. + if (path == null) + { + return null; + } + + // Perf: In most of the common cases, GenerateUrl is called with a null protocol, host and fragment. + // In such cases, we might not need to build any URL as the url generated is mostly same as the virtual path available in pathData. + // For such common cases, this FastGenerateUrl method saves a string allocation per GenerateUrl call. + string url; + if (TryFastGenerateUrl(protocol, host, path, fragment: null, out url)) + { + return url; + } + + var builder = GetStringBuilder(); + try + { + if (string.IsNullOrEmpty(protocol) && string.IsNullOrEmpty(host)) + { + AppendPathAndFragment(builder, pathBase: null, path, fragment: null); + + // We're returning a partial URL (just path + query + fragment), but we still want it to be rooted. + if (builder.Length == 0 || builder[0] != '/') + { + builder.Insert(0, '/'); + } + } + else + { + protocol = string.IsNullOrEmpty(protocol) ? "http" : protocol; + builder.Append(protocol); + + builder.Append("://"); + + host = string.IsNullOrEmpty(host) ? ActionContext.HttpContext.Request.Host.Value : host; + builder.Append(host); + AppendPathAndFragment(builder, pathBase: null, path, fragment: null); + } + + return builder.ToString(); + } + finally + { + // Clear the StringBuilder so that it can reused for the next call. + builder.Clear(); + } + } + // for unit testing internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index a6f92ddc2c..e15b8225e0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -377,7 +377,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var matcherEndpoint = Assert.IsType(endpoint); var routeValuesAddressNameMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressNameMetadata); - Assert.Equal(string.Empty, routeValuesAddressNameMetadata.Name); + Assert.Equal(string.Empty, routeValuesAddressNameMetadata.RouteName); } [Fact] @@ -402,7 +402,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var matcherEndpoint = Assert.IsType(ep); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressMetadata); - Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); + Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName); Assert.Equal("named/Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); }, (ep) => @@ -410,7 +410,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var matcherEndpoint = Assert.IsType(ep); var routeValuesAddressMetadata = matcherEndpoint.Metadata.GetMetadata(); Assert.NotNull(routeValuesAddressMetadata); - Assert.Equal("namedRoute", routeValuesAddressMetadata.Name); + Assert.Equal("namedRoute", routeValuesAddressMetadata.RouteName); Assert.Equal("named/Products/Details/{id?}", matcherEndpoint.RoutePattern.RawText); }); } From 337bc462de22584430592464a14c168fbe2d59de Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 1 Sep 2018 00:53:24 +0100 Subject: [PATCH 219/316] Fast-path async in PagedBufferedTextWriter --- .../Internal/PagedBufferedTextWriter.cs | 63 ++++++++++++++++--- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs index 9148831b6e..303a84c9bb 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs @@ -28,14 +28,28 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal // Don't do anything. We'll call FlushAsync. } - public override async Task FlushAsync() + public override Task FlushAsync() => FlushAsyncCore(); + + // private non-virtual for internal calling + private Task FlushAsyncCore() { var length = _charBuffer.Length; if (length == 0) { - return; + + // If nothing sync buffered return CompletedTask, + // so we can fast-path skip async state-machine creation + return Task.CompletedTask; } + return FlushAsyncAwaited(); + } + + private async Task FlushAsyncAwaited() + { + var length = _charBuffer.Length; + Debug.Assert(length > 0); + var pages = _charBuffer.Pages; var count = pages.Count; for (var i = 0; i < count; i++) @@ -89,21 +103,54 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal _charBuffer.Append(value); } - public override async Task WriteAsync(char value) + public override Task WriteAsync(char value) { - await FlushAsync(); + var flushTask = FlushAsyncCore(); + + // FlushAsyncCore will return CompletedTask if nothing sync buffered + // Fast-path and skip async state-machine if only a single async operation + return ReferenceEquals(flushTask, Task.CompletedTask) ? + _inner.WriteAsync(value) : + WriteAsyncAwaited(flushTask, value); + } + + private async Task WriteAsyncAwaited(Task flushTask, char value) + { + await flushTask; await _inner.WriteAsync(value); } - public override async Task WriteAsync(char[] buffer, int index, int count) + public override Task WriteAsync(char[] buffer, int index, int count) { - await FlushAsync(); + var flushTask = FlushAsyncCore(); + + // FlushAsyncCore will return CompletedTask if nothing sync buffered + // Fast-path and skip async state-machine if only a single async operation + return ReferenceEquals(flushTask, Task.CompletedTask) ? + _inner.WriteAsync(buffer, index, count) : + WriteAsyncAwaited(flushTask, buffer, index, count); + } + + private async Task WriteAsyncAwaited(Task flushTask, char[] buffer, int index, int count) + { + await flushTask; await _inner.WriteAsync(buffer, index, count); } - public override async Task WriteAsync(string value) + public override Task WriteAsync(string value) { - await FlushAsync(); + var flushTask = FlushAsyncCore(); + + // FlushAsyncCore will return CompletedTask if nothing sync buffered + // Fast-path and skip async state-machine if only a single async operation + return ReferenceEquals(flushTask, Task.CompletedTask) ? + _inner.WriteAsync(value) : + WriteAsyncAwaited(flushTask, value); + } + + private async Task WriteAsyncAwaited(Task flushTask, string value) + { + await flushTask; await _inner.WriteAsync(value); } From e174d277bd17232e3ae451b26ea081255051d4c0 Mon Sep 17 00:00:00 2001 From: Ben Adams Date: Sat, 1 Sep 2018 01:13:20 +0100 Subject: [PATCH 220/316] Inline --- .../Internal/PagedBufferedTextWriter.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs index 303a84c9bb..3c436b3277 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.Diagnostics; using System.IO; +using System.Runtime.CompilerServices; using System.Text; using System.Threading.Tasks; @@ -30,7 +31,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public override Task FlushAsync() => FlushAsyncCore(); - // private non-virtual for internal calling + // private non-virtual for internal calling. + // It first does a fast check to see if async is necessary, we inline this check. + [MethodImpl(MethodImplOptions.AggressiveInlining)] private Task FlushAsyncCore() { var length = _charBuffer.Length; From c0f3a46ffebc023b2c4fff6ee5cddde600ebba1a Mon Sep 17 00:00:00 2001 From: Takaaki Suzuki Date: Wed, 5 Sep 2018 18:07:17 +0900 Subject: [PATCH 221/316] Don't call GetValidity method twice. --- .../ModelBinding/ModelStateDictionary.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs index 68781961e6..ad4d64c3ae 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs @@ -147,7 +147,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { get { - return ValidationState == ModelValidationState.Valid || ValidationState == ModelValidationState.Skipped; + var state = ValidationState; + return state == ModelValidationState.Valid || state == ModelValidationState.Skipped; } } From b156dee4f175406a7cdcaaeb2b2a393f2a6abca0 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Fri, 7 Sep 2018 00:39:01 +1000 Subject: [PATCH 222/316] Fix some spelling (#8378) --- .../ActionSelectorBenchmark.cs | 8 +-- .../ActionResultOfT.cs | 2 +- .../ApiConventionMethodAttribute.cs | 2 +- .../ApplicationPartManager.cs | 2 +- .../ClientErrorData.cs | 2 +- .../CompatibilityVersion.cs | 16 ++--- .../ConsumesAttribute.cs | 2 +- .../FromServicesAttribute.cs | 4 +- .../ConfigureCompatibilityOptions.cs | 4 +- ...faultActionDescriptorCollectionProvider.cs | 4 +- .../IActionDescriptorCollectionProvider.cs | 2 +- .../Infrastructure/IActionResultExecutor.cs | 4 +- .../Internal/ActionMethodExecutor.cs | 2 +- .../Internal/ActionSelector.cs | 2 +- .../Internal/MiddlewareFilterBuilder.cs | 4 +- .../Internal/MvcEndpointDataSource.cs | 6 +- .../Binders/CollectionModelBinderProvider.cs | 2 +- .../Binders/ComplexTypeModelBinder.cs | 2 +- .../Binders/EnumTypeModelBinder.cs | 6 +- .../ModelBinding/Binders/HeaderModelBinder.cs | 2 +- .../JQueryKeyValuePairNormalizer.cs | 2 +- .../Metadata/DefaultModelMetadataProvider.cs | 2 +- .../Properties/Resources.Designer.cs | 12 ++-- .../Resources.resx | 58 +++++++++---------- .../UrlHelperExtensions.cs | 2 +- .../ValidationProblemDetails.cs | 2 +- .../MvcJsonOptions.cs | 2 +- .../RazorCompiledItemFeatureProvider.cs | 4 +- .../Compilation/ViewsFeatureProvider.cs | 4 +- .../MvcRazorMvcCoreBuilderExtensions.cs | 2 +- .../Internal/RazorViewCompiler.cs | 4 +- .../RazorProjectPageRouteModelProvider.cs | 4 +- .../Cache/CacheTagKey.cs | 4 +- .../CacheTagHelper.cs | 2 +- .../FormActionTagHelper.cs | 8 +-- .../RenderAtEndOfFormTagHelper.cs | 2 +- .../TagHelperOutputExtensions.cs | 2 +- .../Handlers/RedirectHandler.cs | 2 +- .../WebApplicationFactory.cs | 2 +- .../Controller.cs | 2 +- .../Internal/ViewBuffer.cs | 10 ++-- .../Internal/ViewBufferPage.cs | 2 +- .../ViewFeatures/SaveTempDataAttribute.cs | 6 +- .../DefaultApiDescriptionProviderTest.cs | 8 +-- .../AcceptedAtActionResultTests.cs | 2 +- .../ActionResultOfTTest.cs | 4 +- .../ApiConventionMethodAttributeTest.cs | 4 +- .../ApiExplorer/ApiConventionMatcherTest.cs | 4 +- .../ConsumesAttributeTests.cs | 2 +- .../ControllerBaseTest.cs | 4 +- .../Filters/MiddlewareFilterAttributeTest.cs | 2 +- .../Formatters/FormatFilterTest.cs | 2 +- .../Formatters/NoContentFormatterTests.cs | 2 +- .../Infrastructure/CompatibilitySwitchTest.cs | 4 +- ...tActionDescriptorCollectionProviderTest.cs | 2 +- .../ProblemDetalsClientErrorFactoryTest.cs | 2 +- ...ApiBehaviorApplicationModelProviderTest.cs | 4 +- ...ControllerActionDescriptorProviderTests.cs | 2 +- .../Internal/ControllerActionInvokerTest.cs | 4 +- ...DefaultCollectionValidationStrategyTest.cs | 2 +- .../Internal/DefaultObjectValidatorTests.cs | 4 +- .../DisableRequestSizeLimitFilterTest.cs | 12 ++-- ...itIndexCollectionValidationStrategyTest.cs | 2 +- .../Internal/MiddlewareFilterTest.cs | 8 +-- .../Internal/MvcCoreLoggerExtensionsTest.cs | 6 +- .../Internal/RequestFormLimitsFilterTest.cs | 12 ++-- .../Internal/RequestSizeLimitFilterTest.cs | 12 ++-- .../Binders/BodyModelBinderTests.cs | 2 +- .../Binders/EnumTypeModelBinderTest.cs | 4 +- .../FormCollectionModelBinderProviderTest.cs | 6 +- .../ModelBinding/ModelBindingHelperTest.cs | 2 +- .../RequestFormLimitsAttributeTest.cs | 4 +- .../RequireHttpsAttributeTests.cs | 2 +- .../Routing/KnownRouteValueConstraintTests.cs | 8 +-- .../Routing/UrlHelperExtensionsTest.cs | 2 +- .../CommonResourceInvokerTest.cs | 6 +- .../TestModelMetadataProvider.cs | 6 +- .../JsonPatchOperationsArrayProviderTests.cs | 4 +- .../Internal/EnumerableWrapperProviderTest.cs | 2 +- .../SerializableWrapperProviderFactoryTest.cs | 8 +-- ...ataContractSerializerInputFormatterTest.cs | 4 +- .../XmlSerializerInputFormatterTest.cs | 4 +- .../ApiExplorerTest.cs | 2 +- .../ConsumesAttributeTestsBase.cs | 2 +- .../DataAnnotationTests.cs | 2 +- .../HtmlGenerationTest.cs | 2 +- .../RazorPagesTest.cs | 2 +- .../RazorPagesWithBasePathTest.cs | 8 +-- .../RoutingTestsBase.cs | 2 +- .../SimpleTests.cs | 2 +- .../TagHelpersTest.cs | 2 +- .../WebApiCompatShimBasicTest.cs | 2 +- ...ataContractSerializerInputFormatterTest.cs | 2 +- .../ComplexTypeModelBinderIntegrationTest.cs | 6 +- .../TryValidateModelIntegrationTest.cs | 6 +- .../ValidationIntegrationTests.cs | 8 +-- .../Internal/RazorViewCompilerTest.cs | 4 +- .../RazorPageActivatorTest.cs | 2 +- .../RazorPageTest.cs | 12 ++-- .../RazorViewEngineTest.cs | 6 +- ...izationPageApplicationModelProviderTest.cs | 16 ++--- ...CompiledPageActionDescriptorBuilderTest.cs | 2 +- .../CompiledPageRouteModelProviderTest.cs | 2 +- ...DefaultPageApplicationModelProviderTest.cs | 2 +- .../Internal/PageHandlerResultFilterTest.cs | 2 +- .../PageModelTest.cs | 4 +- .../PageTest.cs | 4 +- .../ImageTagHelperTest.cs | 6 +- .../Internal/AttributeMatcherTest.cs | 2 +- .../MvcServiceCollectionExtensionsTest.cs | 10 ++-- .../ControllerTest.cs | 4 +- .../Internal/ExpressionHelperTest.cs | 2 +- .../ExpressionMetadataProviderTest.cs | 2 +- .../MemberExpressionCacheKeyComparerTest.cs | 8 +-- .../Internal/MemberExpressionCacheKeyTest.cs | 4 +- .../Rendering/HtmlHelperFormExtensionsTest.cs | 8 +-- .../Rendering/HtmlHelperSelectTest.cs | 2 +- .../ViewDataDictionaryOfTModelTest.cs | 8 +-- .../ViewFeatures/ViewDataDictionaryTest.cs | 6 +- .../ViewFeatures/ViewExecutorTest.cs | 6 +- .../TestUtils/TestDataSetAttribute.cs | 8 +-- .../ApiConventionAnalyzerIntegrationTest.cs | 6 +- .../SymbolApiConventionMatcherTest.cs | 10 ++-- .../SymbolApiResponseMetadataProviderTest.cs | 6 +- 124 files changed, 297 insertions(+), 297 deletions(-) diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs index ec9c2c5a4b..5b4cebad21 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance var state = routeContext.RouteData.PushState(MockRouter.Instance, routeValues, null); - var actual = NaiveSelectCandiates(_actions, routeContext.RouteData.Values); + var actual = NaiveSelectCandidates(_actions, routeContext.RouteData.Values); Verify(expected, actual); state.Restore(); @@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance } // A naive implementation we can use to generate match data for inputs, and for a baseline. - private static IReadOnlyList NaiveSelectCandiates(ActionDescriptor[] actions, RouteValueDictionary routeValues) + private static IReadOnlyList NaiveSelectCandidates(ActionDescriptor[] actions, RouteValueDictionary routeValues) { var results = new List(); for (var i = 0; i < actions.Length; i++) @@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance { var action = actions[i]; var routeValues = new RouteValueDictionary(action.RouteValues); - var matches = NaiveSelectCandiates(actions, routeValues); + var matches = NaiveSelectCandidates(actions, routeValues); if (matches.Count == 0) { throw new InvalidOperationException("This should have at least one match."); @@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance // Make one of the route values not match. routeValues[routeValues.First().Key] = ((string)routeValues.First().Value) + "fkdkfdkkf"; - var matches = NaiveSelectCandiates(actions, routeValues); + var matches = NaiveSelectCandidates(actions, routeValues); if (matches.Count != 0) { throw new InvalidOperationException("This should have 0 matches."); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs b/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs index 048c80ff52..048f1be54a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc } /// - /// Intializes a new instance of using the specified . + /// Initializes a new instance of using the specified . /// /// The . public ActionResult(ActionResult result) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs index a54f9a0c07..8efb21d871 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc } else if (methods.Length > 1) { - throw new ArgumentException(Resources.FormatApiConventionMethod_AmbigiousMethodName(methodName, conventionType), nameof(methodName)); + throw new ArgumentException(Resources.FormatApiConventionMethod_AmbiguousMethodName(methodName, conventionType), nameof(methodName)); } return methods[0]; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs index 1636c61df7..99e1c608e1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts /// Gets the list of instances. /// /// Instances in this collection are stored in precedence order. An that appears - /// earlier in the list has a higher precendence. + /// earlier in the list has a higher precedence. /// An may choose to use this an interface as a way to resolve conflicts when /// multiple instances resolve equivalent feature values. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs b/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs index 38b3448ece..9b292ad53d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs @@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.Mvc ///
/// /// By default, this maps to and should not change - /// between multiple occurences of the same error. + /// between multiple occurrences of the same error. /// public string Title { get; set; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs index 0260f464a7..ad18d058a6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs @@ -6,11 +6,11 @@ using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Mvc { /// - /// Specifies the version compatibility of runtime behaviors configured by . + /// Specifies the version compatibility of runtime behaviors configured by . /// /// /// - /// The best way to set a compatibility version is by using + /// The best way to set a compatibility version is by using /// or /// in your application's /// ConfigureServices method. @@ -20,32 +20,32 @@ namespace Microsoft.AspNetCore.Mvc /// public class Startup /// { /// ... - /// + /// /// public void ConfigureServices(IServiceCollection services) /// { /// services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); /// } - /// + /// /// ... /// } /// /// /// /// - /// Setting compatiblity version to a specific version will change the default values of various - /// settings to match a particular minor release of ASP.NET Core MVC. + /// Setting compatibility version to a specific version will change the default values of various + /// settings to match a particular minor release of ASP.NET Core MVC. /// /// public enum CompatibilityVersion { /// - /// Sets the default value of settings on to match the behavior of + /// Sets the default value of settings on to match the behavior of /// ASP.NET Core MVC 2.0. /// Version_2_0, /// - /// Sets the default value of settings on to match the behavior of + /// Sets the default value of settings on to match the behavior of /// ASP.NET Core MVC 2.1. /// /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs index 7af167dc51..30a103a531 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Mvc // If there are multiple IConsumeActionConstraints which are defined at the class and // at the action level, the one closest to the action overrides the others. To ensure this // we take advantage of the fact that ConsumesAttribute is both an IActionFilter and an - // IConsumeActionConstraint. Since filterdescriptor collection is ordered (the last filter is the one + // IConsumeActionConstraint. Since FilterDescriptor collection is ordered (the last filter is the one // closest to the action), we apply this constraint only if there is no IConsumeActionConstraint after this. return actionDescriptor.FilterDescriptors.Last( filter => filter.Filter is IConsumesActionConstraint).Filter == this; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs index 271ae181f2..31e299de73 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs @@ -16,9 +16,9 @@ namespace Microsoft.AspNetCore.Mvc /// /// /// [HttpGet] - /// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelReqest) + /// public ProductModel GetProduct([FromServices] IProductModelRequestService productModelRequest) /// { - /// return productModelReqest.Value; + /// return productModelRequest.Value; /// } /// /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs index c46a3529d0..33009c95de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.Infrastructure { /// - /// A base class for infrastructure that implements ASP.NET Core MVC's support for + /// A base class for infrastructure that implements ASP.NET Core MVC's support for /// . This is framework infrastructure and should not be used /// by application code. /// @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure throw new ArgumentNullException(nameof(options)); } - // Evaluate DefaultValues onces so subclasses don't have to cache. + // Evaluate DefaultValues once so subclasses don't have to cache. var defaultValues = DefaultValues; foreach (var @switch in options) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs index 6204ce3945..9dce3d6f5a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure return new CompositeChangeToken(changeTokens); } - + private void Initialize() { // Using double-checked locking on initialization because we fire change token callbacks @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure // Consumers who poll will observe a new action descriptor collection at step 2 - they will see // the new collection and ignore the change token. // - // Consumers who listen to the change token will requery at step 4 - they will see the new collection + // Consumers who listen to the change token will re-query at step 4 - they will see the new collection // and new change token. // // Anyone who acquires the collection and change token between steps 2 and 3 will be notified of diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs index ee1648e170..c109d78846 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// /// /// To be reactively notified of changes, downcast to and - /// subcribe to the change token returned from + /// subscribe to the change token returned from /// using . /// /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs index 3a4e398c5e..99d01f81d2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs @@ -12,14 +12,14 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure ///
/// The type of . /// - /// Implementions of are typically called by the + /// Implementations of are typically called by the /// method of the corresponding action result type. /// Implementations should be registered as singleton services. /// public interface IActionResultExecutor where TResult : IActionResult { /// - /// Asynchronously excecutes the action result, by modifying the . + /// Asynchronously executes the action result, by modifying the . /// /// The associated with the current request."/> /// The action result to execute. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs index 28541b3e55..806b97d1e5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs @@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal => typeof(Task).IsAssignableFrom(executor.MethodReturnType); } - // Task DownloadFile(..) + // Task DownloadFile(..) // ValueTask GetViewsAsync(..) private class TaskOfActionResultExecutor : ActionMethodExecutor { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs index 0f9a1cb6e4..210250ff69 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs @@ -365,7 +365,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // We also want to add the same (as in reference equality) list of actions to the ordinal entries. // We'll keep updating `entries` to include all of the actions in the same equivalence class - - // meaning, all conventionally routed actions for which the route values are equalignoring case. + // meaning, all conventionally routed actions for which the route values are equal ignoring case. // // `entries` will appear in `OrdinalIgnoreCaseEntries` exactly once and in `OrdinalEntries` once // for each variation of casing that we've seen. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs index cbceab72b0..6257968f90 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public RequestDelegate GetPipeline(Type configurationType) { - // Build the pipeline only once. This is similar to how middlewares registered in Startup are constructed. + // Build the pipeline only once. This is similar to how middleware registered in Startup are constructed. var requestDelegate = _pipelinesCache.GetOrAdd( configurationType, @@ -77,7 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } // Ideally we want the experience of a middleware pipeline to behave the same as if it was registered - // in Startup. In this scenario, an Exception thrown in a middelware later in the pipeline gets + // in Startup. In this scenario, an Exception thrown in a middleware later in the pipeline gets // propagated back to earlier middleware. So, check if a later resource filter threw an Exception and // propagate that back to the middleware pipeline. resourceExecutedContext.ExceptionDispatchInfo?.Throw(); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index c99e3a216d..4c0304e380 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private readonly DefaultHttpContext _httpContextInstance; // The following are protected by this lock for WRITES only. This pattern is similar - // to DefaultActionDescriptorChangeProvider - see comments there for details on + // to DefaultActionDescriptorChangeProvider - see comments there for details on // all of the threading behaviors. private readonly object _lock = new object(); private List _endpoints; @@ -162,7 +162,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // - /Home/Index/{id?} // - /Home // - / - if (UseDefaultValuePlusRemainingSegementsOptional(i, action, endpointInfo, newPathSegments)) + if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, endpointInfo, newPathSegments)) { var subPathSegments = newPathSegments.Take(i); @@ -270,7 +270,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private bool UseDefaultValuePlusRemainingSegementsOptional( + private bool UseDefaultValuePlusRemainingSegmentsOptional( int segmentIndex, ActionDescriptor action, MvcEndpointInfo endpointInfo, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs index 0617122b5f..bacc1f371f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs @@ -45,7 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } // If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since - // that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyColection<> and + // that's what we would create. (The cases handled here are IEnumerable<>, IReadOnlyCollection<> and // IReadOnlyList<>). var enumerableType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(IEnumerable<>)); if (enumerableType != null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs index dd02365c96..3f15ffdf20 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs @@ -256,7 +256,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // // If a property does not have a binding source, then it's fair game for any value provider. // - // If any property meets the above conditions and has a value from valueproviders, then we'll + // If any property meets the above conditions and has a value from ValueProviders, then we'll // create the model and try to bind it. OR if ALL properties of the model have a greedy source, // then we go ahead and create it. // diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs index 601d02ba44..f6c99138fd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs @@ -82,11 +82,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // enum FlagsEnum { Value1 = 1, Value2 = 2, Value4 = 4 } // // Valid Scenarios: - // 1. valueproviderresult="Value2,Value4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 - // 2. valueproviderresult="2,4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 + // 1. valueProviderResult="Value2,Value4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 + // 2. valueProviderResult="2,4", model=Value2 | Value4, underlying=6, converted=Value2, Value4 // // Invalid Scenarios: - // 1. valueproviderresult="2,10", model=12, underlying=12, converted=12 + // 1. valueProviderResult="2,10", model=12, underlying=12, converted=12 // var underlying = Convert.ChangeType( model, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs index 48c3ae2304..e9de376246 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs @@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var headerName = bindingContext.FieldName; // Do not set ModelBindingResult to Failed on not finding the value in the header as we want the inner - // modelbinder to do that. This would give a chance to the inner binder to add more useful information. + // ModelBinder to do that. This would give a chance to the inner binder to add more useful information. // For example, SimpleTypeModelBinder adds a model error when binding to let's say an integer and the // model is null. var request = bindingContext.HttpContext.Request; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs index b0c1c28c0f..0fdd6c2634 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs @@ -9,7 +9,7 @@ using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Mvc.ModelBinding { - // Normalizes keys, in a keyvaluepair collection, from jQuery format to a format that MVC understands. + // Normalizes keys, in a KeyValuePair collection, from jQuery format to a format that MVC understands. internal static class JQueryKeyValuePairNormalizer { public static IDictionary GetValues( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs index 75bd188997..e7a2d454d8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata var cacheEntry = GetCacheEntry(modelType); // We're relying on a safe race-condition for Properties - take care only - // to set the value onces the properties are fully-initialized. + // to set the value once the properties are fully-initialized. if (cacheEntry.Details.Properties == null) { var key = ModelMetadataIdentity.ForType(modelType); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index d70a92f526..70d895c8af 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1509,18 +1509,18 @@ namespace Microsoft.AspNetCore.Mvc.Core => string.Format(CultureInfo.CurrentCulture, GetString("ApiConvention_UnsupportedAttributesOnConvention"), p0, p1, p2); /// - /// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + /// Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'. /// - internal static string ApiConventionMethod_AmbigiousMethodName + internal static string ApiConventionMethod_AmbiguousMethodName { - get => GetString("ApiConventionMethod_AmbigiousMethodName"); + get => GetString("ApiConventionMethod_AmbiguousMethodName"); } /// - /// Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + /// Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'. /// - internal static string FormatApiConventionMethod_AmbigiousMethodName(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_AmbigiousMethodName"), p0, p1); + internal static string FormatApiConventionMethod_AmbiguousMethodName(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("ApiConventionMethod_AmbiguousMethodName"), p0, p1); /// /// A method named '{0}' was not found on convention type '{1}'. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index cc88759296..5ec7fea828 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -1,17 +1,17 @@  - @@ -451,8 +451,8 @@ Method {0} is decorated with the following attributes that are not allowed on an API convention method:{1}The following attributes are allowed on API convention methods: {2}. - - Method name '{0}' is ambigous for convention type '{1}'. More than one method found with the name '{0}'. + + Method name '{0}' is ambiguous for convention type '{1}'. More than one method found with the name '{0}'. A method named '{0}' was not found on convention type '{1}'. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs index af43e847bb..42da1bc1ac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -462,7 +462,7 @@ namespace Microsoft.AspNetCore.Mvc if (!routeValues.ContainsKey("handler") && ambientValues.TryGetValue("handler", out var handler)) { - // Clear out formaction unless it's explicitly specified in the routeValues. + // Clear out form action unless it's explicitly specified in the routeValues. routeValues["handler"] = null; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs index b27e790a90..2332367b95 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc public class ValidationProblemDetails : ProblemDetails { /// - /// Intializes a new instance of . + /// Initializes a new instance of . /// public ValidationProblemDetails() { diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs index f27cf0b00c..28df9825b5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc } /// - /// Gets or sets a flag to determine whether error messsages from JSON deserialization by the + /// Gets or sets a flag to determine whether error messages from JSON deserialization by the /// will be added to the . The default /// value is false, meaning that a generic error message will be used instead. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs index d59c4e9ffb..ccfc49127d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs @@ -25,12 +25,12 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts if (duplicates != null) { - var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier)); + var viewsDifferingInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.Identifier)); var message = string.Join( Environment.NewLine, Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, - viewsDiffereningInCase); + viewsDifferingInCase); throw new InvalidOperationException(message); } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs index deb57205de..e7e7143fbb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs @@ -35,12 +35,12 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Compilation { // Ensure parts do not specify views with differing cases. This is not supported // at runtime and we should flag at as such for precompiled views. - var viewsDiffereningInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath)); + var viewsDifferingInCase = string.Join(Environment.NewLine, duplicates.Select(d => d.RelativePath)); var message = string.Join( Environment.NewLine, Resources.RazorViewCompiler_ViewPathsDifferOnlyInCase, - viewsDiffereningInCase); + viewsDifferingInCase); throw new InvalidOperationException(message); } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index fa30c8eddd..4960f18b41 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -81,7 +81,7 @@ namespace Microsoft.Extensions.DependencyInjection // ViewFeature items have precedence semantics - when two views have the same path \ identifier, // the one that appears earlier in the list wins. Therefore the ordering of // RazorCompiledItemFeatureProvider and ViewsFeatureProvider is pertinent - any view compiled - // using the Sdk will be prefered to views compiled using MvcPrecompilation. + // using the Sdk will be preferred to views compiled using MvcPrecompilation. if (!builder.PartManager.FeatureProviders.OfType().Any()) { builder.PartManager.FeatureProviders.Add(new RazorCompiledItemFeatureProvider()); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs index 1f4d30b159..3d107018c2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal // from either the set of known precompiled views, or by being compiled. _cache = cache; - // We need to validate that the all of the precompiled views are unique by path (case-insenstive). + // We need to validate that the all of the precompiled views are unique by path (case-insensitive). // We do this because there's no good way to canonicalize paths on windows, and it will create // problems when deploying to linux. Rather than deal with these issues, we just don't support // views that differ only by case. @@ -293,7 +293,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _logger.ViewCompilerCouldNotFindFileAtPath(normalizedPath); // If the file doesn't exist, we can't do compilation right now - we still want to cache - // the fact that we tried. This will allow us to retrigger compilation if the view file + // the fact that we tried. This will allow us to re-trigger compilation if the view file // is added. return new ViewCompilerWorkItem() { diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs index 81cf7b6a42..ad8187cb78 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting(PageRouteModelProviderContext context) { - // When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectoryy = /Areas; + // When RootDirectory and AreaRootDirectory overlap, e.g. RootDirectory = /, AreaRootDirectory = /Areas; // we need to ensure that the page is only route-able via the area route. By adding area routes first, // we'll ensure non area routes get skipped when it encounters an IsAlreadyRegistered check. @@ -128,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private static bool IsRouteable(RazorProjectItem item) { - // Pages like _ViewImports should not be routable. + // Pages like _ViewImports should not be routeable. return !item.FileName.StartsWith("_", StringComparison.OrdinalIgnoreCase); } } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs index 3d1912cba0..00647c6e2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache public class CacheTagKey : IEquatable { private static readonly char[] AttributeSeparator = new[] { ',' }; - private static readonly Func CookieAcccessor = (c, key) => c[key]; + private static readonly Func CookieAccessor = (c, key) => c[key]; private static readonly Func HeaderAccessor = (c, key) => c[key]; private static readonly Func QueryAccessor = (c, key) => c[key]; private static readonly Func RouteValueAccessor = (c, key) => c[key]?.ToString(); @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Cache _expiresOn = tagHelper.ExpiresOn; _expiresSliding = tagHelper.ExpiresSliding; _varyBy = tagHelper.VaryBy; - _cookies = ExtractCollection(tagHelper.VaryByCookie, request.Cookies, CookieAcccessor); + _cookies = ExtractCollection(tagHelper.VaryByCookie, request.Cookies, CookieAccessor); _headers = ExtractCollection(tagHelper.VaryByHeader, request.Headers, HeaderAccessor); _queries = ExtractCollection(tagHelper.VaryByQuery, request.Query, QueryAccessor); _routeValues = ExtractCollection(tagHelper.VaryByRoute, tagHelper.ViewContext.RouteData.Values, RouteValueAccessor); diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs index 09beddee8a..32f0824d04 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // can't be put inside a using block. entry.Dispose(); - // Set the result on the TCS once we've commited the entry to the cache since commiting to the cache + // Set the result on the TCS once we've committed the entry to the cache since commiting to the cache // may throw. tcs.SetResult(content); return content; diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs index 18c89f37e4..66d22ea10c 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs @@ -167,9 +167,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } /// - /// Does nothing if user provides an formaction attribute. + /// Does nothing if user provides an FormAction attribute. /// - /// Thrown if formaction attribute is provided and , , + /// Thrown if FormAction attribute is provided and , , /// or are non-null or if the user provided asp-route-* attributes. /// Also thrown if and one or both of and /// are non-null @@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers throw new ArgumentNullException(nameof(output)); } - // If "formaction" is already set, it means the user is attempting to use a normal button or input element. + // If "FormAction" is already set, it means the user is attempting to use a normal button or input element. if (output.Attributes.ContainsName(FormAction)) { if (Action != null || @@ -198,7 +198,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Route != null || (_routeValues != null && _routeValues.Count > 0)) { - // User specified a formaction and one of the bound attributes; can't override that formaction + // User specified a FormAction and one of the bound attributes; can't override that FormAction // attribute. throw new InvalidOperationException( Resources.FormatFormActionTagHelper_CannotOverrideFormAction( diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs index d1e2537044..c819a38c98 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public class RenderAtEndOfFormTagHelper : TagHelper { // This TagHelper's order must be greater than the FormTagHelper's. I.e it must be executed after - // FormTaghelper does. + // FormTagHelper does. /// public override int Order => -900; diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs index 8075b5bf0e..91be1e59de 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var copiedAttribute = false; // We iterate context.AllAttributes backwards since we prioritize TagHelperOutput values occurring - // before the current context.AllAttribtes[i]. + // before the current context.AllAttributes[i]. for (var i = context.AllAttributes.Count - 1; i >= 0; i--) { // We look for the original attribute so we can restore the exact attribute name the user typed in diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs index 71198d3d75..8af03645ac 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing.Handlers /// /// Creates a new instance of . /// - /// The maximun number of redirect responses to follow. It must be + /// The maximum number of redirect responses to follow. It must be /// equal or greater than 0. public RedirectHandler(int maxRedirects) { diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index c04fe25f45..356bd56d97 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc.Testing /// on the assembly containing the functional tests with /// a key equal to the assembly . /// In case an attribute with the right key can't be found, - /// will fall back to searching for a solution file (*.sln) and then appending asembly name + /// will fall back to searching for a solution file (*.sln) and then appending assembly name /// to the solution directory. The application root directory will be used to discover views and content files. /// /// diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs index 5c680ffaaa..c0aaa0e3b3 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc /// Gets or sets used by and . /// /// - /// By default, this property is intiailized when activates + /// By default, this property is initialized when activates /// controllers. /// /// This property can be accessed after the controller has been activated, for example, in a controller action diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs index 7a39276b7b..dfe23dce7c 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs @@ -91,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } /// - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder Append(string unencoded) { @@ -106,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } /// - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder AppendHtml(IHtmlContent content) { @@ -119,7 +119,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } /// - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] public IHtmlContentBuilder AppendHtml(string encoded) { @@ -131,7 +131,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal return this; } - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] private void AppendValue(ViewBufferValue value) { @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal page.Append(value); } - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] private ViewBufferPage GetCurrentPage() { diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs index 416e329bd7..dfc47673ec 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public bool IsFull => Count == Capacity; - // Very common trival method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 + // Very common trivial method; nudge it to inline https://github.com/aspnet/Mvc/pull/8339 [MethodImpl(MethodImplOptions.AggressiveInlining)] public void Append(ViewBufferValue value) => Buffer[Count++] = value; } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs index 908afc2fca..1c9af6ca28 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs @@ -16,13 +16,13 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures { public SaveTempDataAttribute() { - // Since SaveTempDataFilter registers for a response's OnStarting callback, we want this filter to run - // as early as possible to get the opportunity to register the call back before any other result filter + // Since SaveTempDataFilter registers for a response's OnStarting callback, we want this filter to run + // as early as possible to get the opportunity to register the call back before any other result filter // starts writing to the response stream. Order = int.MinValue + 100; } - // + /// public int Order { get; set; } /// diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index 224a6b1276..e4a446485d 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -1565,10 +1565,10 @@ namespace Microsoft.AspNetCore.Mvc.Description } [Fact] - public void GetApiDescription_ParameterDescription_RedundentMetadata_NotMergedWithParent() + public void GetApiDescription_ParameterDescription_RedundantMetadata_NotMergedWithParent() { // Arrange - var action = CreateActionDescriptor(nameof(AcceptsRedundentMetadata)); + var action = CreateActionDescriptor(nameof(AcceptsRedundantMetadata)); var parameterDescriptor = action.Parameters.Single(); // Act @@ -1590,7 +1590,7 @@ namespace Microsoft.AspNetCore.Mvc.Description } [Fact] - public void GetApiDescription_ParameterDescription_RedundentMetadata_WithParameterMetadata() + public void GetApiDescription_ParameterDescription_RedundantMetadata_WithParameterMetadata() { // Arrange var action = CreateActionDescriptor(nameof(AcceptsPerson)); @@ -2081,7 +2081,7 @@ namespace Microsoft.AspNetCore.Mvc.Description { } - private void AcceptsRedundentMetadata([FromQuery] RedundentMetadata r) + private void AcceptsRedundantMetadata([FromQuery] RedundentMetadata r) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs index d6ab7ad228..242405e9ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs @@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Mvc } [Fact] - public void OnFormatting_NullUrlHelperContextNoRequestServices_ThrowsArgumentNullExeption() + public void OnFormatting_NullUrlHelperContextNoRequestServices_ThrowsArgumentNullException() { // Arrange var context = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs index 8f5abec7e5..83e1058b0c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc public void Convert_InfersDeclaredTypeFromActionResultTypeParameter() { // Arrange - var value = new DeriviedItem(); + var value = new DerivedItem(); var actionResultOfT = new ActionResult(value); var convertToActionResult = (IConvertToActionResult)actionResultOfT; @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc { } - private class DeriviedItem : BaseItem + private class DerivedItem : BaseItem { } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs index 01fc4f3773..cba34f7b96 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs @@ -85,14 +85,14 @@ namespace Microsoft.AspNetCore.Mvc } [Fact] - public void Constructor_ThrowsIfMethodIsAmbigous() + public void Constructor_ThrowsIfMethodIsAmbiguous() { // Arrange var methodName = typeof(ConventionWithProducesAttribute).FullName + '.' + nameof(ConventionWithProducesAttribute.Get); var attribute = typeof(ProducesAttribute); var type = typeof(TestConventions); - var expected = $"Method name 'Method' is ambigous for convention type '{type}'. More than one method found with the name 'Method'."; + var expected = $"Method name 'Method' is ambiguous for convention type '{type}'. More than one method found with the name 'Method'."; // Act & Assert ExceptionAssert.ThrowsArgument( diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs index 72526bbd2b..bbf31785a0 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrefix() { // Arrange var name = "Postman"; @@ -386,7 +386,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } [Fact] - public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() + public void IsMatch_ReturnsTrue_IfMethodNameAndParametersMatches() { // Arrange var method = typeof(TestController).GetMethod(nameof(TestController.Get)); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs index be1999d28d..1ecf35fb8e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc [InlineData("application/json")] [InlineData("application/json;Parameter1=12")] [InlineData("text/xml")] - public void ActionConstraint_Accept_MatchesForMachingRequestContentType(string contentType) + public void ActionConstraint_Accept_MatchesForMatchingRequestContentType(string contentType) { // Arrange var constraint = new ConsumesAttribute("application/json", "text/xml"); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs index d26aa506c7..659260734a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs @@ -1027,10 +1027,10 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test // Arrange var controller = new TestableController(); var pageName = "/Page-Name"; - var routeVaues = new { key = "value" }; + var routeValues = new { key = "value" }; // Act - var result = controller.RedirectToPage(pageName, routeVaues); + var result = controller.RedirectToPage(pageName, routeValues); // Assert Assert.IsType(result); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs index bedfd14f4a..bcfec63de6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Pipeline1.ConfigurePipeline = (ab) => { configureCallCount++; - ab.Use((httpCtxt, next) => + ab.Use((httpContext, next) => { return next(); }); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs index 9610b485b4..84778f73b3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters httpContext.Setup(c => c.Request.Query.ContainsKey("format")).Returns(true); httpContext.Setup(c => c.Request.Query["format"]).Returns("xml"); - // Routedata contains json + // RouteData contains json var data = new RouteData(); data.Values.Add("format", "json"); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs index 408806e727..b8e1d51bc2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters { get { - // object value, bool useDeclaredTypeAsString, bool expectedCanwriteResult, bool useNonNullContentType + // object value, bool useDeclaredTypeAsString, bool expectedCanWriteResult, bool useNonNullContentType yield return new object[] { "valid value", true, false, true }; yield return new object[] { "valid value", false, false, true }; yield return new object[] { "", false, false, true }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs index 119113f668..aa91274e77 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure } [Fact] - public void Constructor_WithNameAndInitalValue_IsValueSetIsFalse() + public void Constructor_WithNameAndInitialValue_IsValueSetIsFalse() { // Arrange & Act var @switch = new CompatibilitySwitch("TestProperty", initialValue: true); @@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure var @switch = new CompatibilitySwitch("TestProperty"); // Act - @switch.Value = false; // You don't need to actually change the value, just caling the setting works + @switch.Value = false; // You don't need to actually change the value, just calling the setting works // Assert Assert.False(@switch.Value); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs index cac4f58aec..e5eb4b7f94 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure } [Fact] - public void ActionDescriptors_UpdatesAndResubscripes_WhenChangeTokenTriggers() + public void ActionDescriptors_UpdatesAndResubscribes_WhenChangeTokenTriggers() { // Arrange var actionDescriptorProvider = new Mock(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs index 603b60e381..3a84deabe7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs @@ -8,7 +8,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.Infrastructure { - public class ProblemDetalsClientErrorFactoryTest + public class ProblemDetailsClientErrorFactoryTest { [Fact] public void GetClientError_ReturnsProblemDetails_IfNoMappingWasFound() diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs index c4104b9027..23241174ca 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs @@ -227,7 +227,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MulitpleRoutes() + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MultipleRoutes() { // Arrange var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes); @@ -1112,7 +1112,7 @@ Environment.NewLine + "int b"; } [Fact] - public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAsssembly() + public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAssembly() { // Arrange var expected = typeof(InvalidEnumArgumentException); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 060543dd8b..9dbd548266 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -1175,7 +1175,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [InlineData("A", typeof(ApiExplorerEnabledConventionalRoutedController))] [InlineData("A", typeof(ApiExplorerEnabledActionConventionalRoutedController))] - public void ApiExplorer_ThrowsForContentionalRouting(string actionName, Type type) + public void ApiExplorer_ThrowsForConventionalRouting(string actionName, Type type) { // Arrange var assemblyName = type.GetTypeInfo().Assembly.GetName().Name; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index 9d5c551ce1..74090a932c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -1206,7 +1206,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Theory] - [InlineData(nameof(TestController.AsynActionMethodWithTestActionResult))] + [InlineData(nameof(TestController.AsyncActionMethodWithTestActionResult))] [InlineData(nameof(TestController.ActionMethodWithTestActionResult))] public async Task InvokeAction_ReturnTypeAsIActionResult_ReturnsExpected(string methodName) { @@ -1654,7 +1654,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return new TestActionResult { Value = value }; } - public async Task AsynActionMethodWithTestActionResult(int value) + public async Task AsyncActionMethodWithTestActionResult(int value) { return await Task.FromResult(new TestActionResult { Value = value }); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs index 70a6fb110d..30f964b93d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void EnumerateElements_TwoEnumerableImplemenations() + public void EnumerateElements_TwoEnumerableImplementations() { // Arrange var model = new TwiceEnumerable(new int[] { 2, 3, 5 }); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs index fad6540560..5450c507c1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs @@ -1316,7 +1316,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { { model, new ValidationStateEntry() } }; - var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLeveleMetadataData), BindingFlags.NonPublic | BindingFlags.Instance); + var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance); var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]); // Act & Assert @@ -1465,7 +1465,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal void DoSomething(); } - private void Validate_Throws_ForTopLeveleMetadataData(DepthObject depthObject) { } + private void Validate_Throws_ForTopLevelMetadataData(DepthObject depthObject) { } // Custom validation attribute that returns multiple entries in ValidationResult.MemberNames and those member // names are indexers. An example scenario is an attribute that confirms all entries in a list are unique. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs index 4340460963..6e8907f1a5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Arrange var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); @@ -37,7 +37,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Arrange var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); var disableRequestSizeLimitResourceFilterFinal = new DisableRequestSizeLimitFilter(NullLoggerFactory.Instance); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { disableRequestSizeLimitResourceFilter, disableRequestSizeLimitResourceFilterFinal }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var loggerFactory = new TestLoggerFactory(sink, enabled: true); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); // Act disableRequestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var loggerFactory = new TestLoggerFactory(sink, enabled: true); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); httpMaxRequestBodySize.IsReadOnly = true; @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var loggerFactory = new TestLoggerFactory(sink, enabled: true); var disableRequestSizeLimitResourceFilter = new DisableRequestSizeLimitFilter(loggerFactory); - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { disableRequestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); @@ -114,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal($"The request body size limit has been disabled.", write.State.ToString()); } - private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) + private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters) { return new AuthorizationFilterContext(CreateActionContext(), filters); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs index deeb9f74ae..cf1fecf8f3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void EnumerateElements_TwoEnumerableImplemenations() + public void EnumerateElements_TwoEnumerableImplementations() { // Arrange var model = new TwiceEnumerable(new int[] { 2, 3, 5 }); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs index e6c748784e..5edff31ab3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs @@ -36,13 +36,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Arrange Task requestDelegate(HttpContext context) => Task.FromResult(true); - var middlwareFilter = new MiddlewareFilter(requestDelegate); + var middlewareFilter = new MiddlewareFilter(requestDelegate); var httpContext = new DefaultHttpContext(); var resourceExecutingContext = GetResourceExecutingContext(httpContext); var resourceExecutionDelegate = GetResourceExecutionDelegate(httpContext); // Act - await middlwareFilter.OnResourceExecutionAsync(resourceExecutingContext, resourceExecutionDelegate); + await middlewareFilter.OnResourceExecutionAsync(resourceExecutingContext, resourceExecutionDelegate); // Assert var feature = resourceExecutingContext.HttpContext.Features.Get(); @@ -398,7 +398,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal logger, diagnosticSource, mapper, - CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), + CreateControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), CreateCacheEntry((ControllerActionDescriptor)actionContext.ActionDescriptor, controllerFactory), filters) { @@ -420,7 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo); } - private static ControllerContext CreatControllerContext( + private static ControllerContext CreateControllerContext( ActionContext actionContext, IReadOnlyList valueProviderFactories, int maxAllowedErrorsInModelState) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs index 8835d2da8e..2b56c00572 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs @@ -112,9 +112,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal var asyncResultFilter = Mock.Of(); var resourceFilter = Mock.Of(); var asyncResourceFilter = Mock.Of(); - var orderedresourceFilterMock = new Mock(); - orderedresourceFilterMock.SetupGet(f => f.Order).Returns(-100); - var orderedResourceFilter = orderedresourceFilterMock.Object; + var orderedResourceFilterMock = new Mock(); + orderedResourceFilterMock.SetupGet(f => f.Order).Returns(-100); + var orderedResourceFilter = orderedResourceFilterMock.Object; var filters = new IFilterMetadata[] { actionFilter, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs index a36dd060e4..7b1234092b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Arrange var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance); requestFormLimitsFilter.FormOptions = new FormOptions(); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestFormLimitsFilter }); // Set to null explicitly as we want to make sure the filter adds one authorizationFilterContext.HttpContext.Features.Set(null); @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Arrange var requestFormLimitsFilter = new RequestFormLimitsFilter(NullLoggerFactory.Instance); requestFormLimitsFilter.FormOptions = new FormOptions(); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestFormLimitsFilter }); var oldFormFeature = new FormFeature(authorizationFilterContext.HttpContext.Request); // Set to null explicitly as we want to make sure the filter adds one @@ -63,7 +63,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); requestFormLimitsFilter.FormOptions = new FormOptions(); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestFormLimitsFilter }); authorizationFilterContext.HttpContext.Request.Form = new FormCollection(null); @@ -87,7 +87,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); requestFormLimitsFilter.FormOptions = new FormOptions(); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestFormLimitsFilter }); // Set to null explicitly as we want to make sure the filter adds one authorizationFilterContext.HttpContext.Features.Set(null); @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestFormLimitsFilter = new RequestFormLimitsFilter(loggerFactory); requestFormLimitsFilter.FormOptions = new FormOptions(); - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestFormLimitsFilter }); // Set to null explicitly as we want to make sure the filter adds one authorizationFilterContext.HttpContext.Features.Set( @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal write.State.ToString()); } - private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) + private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters) { return new AuthorizationFilterContext(CreateActionContext(), filters); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs index 82c040508d..80be3c2e9a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Arrange var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(NullLoggerFactory.Instance); requestSizeLimitResourceFilter.Bytes = 12345; - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal requestSizeLimitResourceFilter.Bytes = 12345; var requestSizeLimitResourceFilterFinal = new RequestSizeLimitFilter(NullLoggerFactory.Instance); requestSizeLimitResourceFilterFinal.Bytes = 0; - var authorizationFilterContext = CreateauthorizationFilterContext( + var authorizationFilterContext = CreateAuthorizationFilterContext( new IFilterMetadata[] { requestSizeLimitResourceFilter, requestSizeLimitResourceFilterFinal }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); @@ -64,7 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); // Act requestSizeLimitResourceFilter.OnAuthorization(authorizationFilterContext); @@ -84,7 +84,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); httpMaxRequestBodySize.IsReadOnly = true; @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var requestSizeLimitResourceFilter = new RequestSizeLimitFilter(loggerFactory); requestSizeLimitResourceFilter.Bytes = 12345; - var authorizationFilterContext = CreateauthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); + var authorizationFilterContext = CreateAuthorizationFilterContext(new IFilterMetadata[] { requestSizeLimitResourceFilter }); var httpMaxRequestBodySize = new TestHttpMaxRequestBodySizeFeature(); authorizationFilterContext.HttpContext.Features.Set(httpMaxRequestBodySize); @@ -120,7 +120,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal($"The maximum request body size has been set to 12345.", write.State.ToString()); } - private static AuthorizationFilterContext CreateauthorizationFilterContext(IFilterMetadata[] filters) + private static AuthorizationFilterContext CreateAuthorizationFilterContext(IFilterMetadata[] filters) { return new AuthorizationFilterContext(CreateActionContext(), filters); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs index 21a66b0a40..71318bc1c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs @@ -467,7 +467,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders [Theory] [MemberData(nameof(DerivedInputFormattersThrowingNonInputFormatterException))] - public async Task BindModel_DerivedXmlInputFormatters_ThrowingNonInputFormatingException_AddsErrorToModelState( + public async Task BindModel_DerivedXmlInputFormatters_ThrowingNonInputFormattingException_AddsErrorToModelState( IInputFormatter formatter, string contentType, InputFormatterExceptionPolicy inputFormatterExceptionPolicy) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs index e7c4a2bf2b..aa8f50aa79 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs @@ -42,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding [InlineData(false, typeof(IntEnum))] [InlineData(false, typeof(FlagsEnum))] public async Task BindModel_AddsErrorToModelState_ForEmptyValue_AndNonNullableEnumTypes( - bool suprressBindingUndefinedValueToEnumType, + bool suppressBindingUndefinedValueToEnumType, Type modelType) { // Arrange var message = "The value '' is invalid."; var binderInfo = GetBinderAndContext( modelType, - suprressBindingUndefinedValueToEnumType, + suppressBindingUndefinedValueToEnumType, valueProviderValue: ""); var bindingContext = binderInfo.Item1; var binder = binderInfo.Item2; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs index 8562af3410..38b5a0d65d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { [Theory] [InlineData(typeof(FormCollection))] - [InlineData(typeof(DerviedFormCollection))] + [InlineData(typeof(DerivedFormCollection))] public void Create_ThrowsException_ForFormCollectionModelType(Type modelType) { // Arrange @@ -62,9 +62,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { } - private class DerviedFormCollection : FormCollection + private class DerivedFormCollection : FormCollection { - public DerviedFormCollection() : base(fields: null, files: null) { } + public DerivedFormCollection() : base(fields: null, files: null) { } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs index 116224e517..eab8c6a860 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs @@ -607,7 +607,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding } [Fact] - public async Task TryUpdataModel_ModelTypeDifferentFromModel_Throws() + public async Task TryUpdateModel_ModelTypeDifferentFromModel_Throws() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs index 8b8cbfa48d..df9eae7c9c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs @@ -21,10 +21,10 @@ namespace Microsoft.AspNetCore.Mvc // Act & Assert foreach (var property in formOptionsProperties) { - var formLimiAttributeProperty = formLimitsAttributeProperties + var formLimitAttributeProperty = formLimitsAttributeProperties .Where(pi => property.Name == pi.Name && pi.PropertyType == property.PropertyType) .SingleOrDefault(); - Assert.NotNull(formLimiAttributeProperty); + Assert.NotNull(formLimitAttributeProperty); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs index bac3672432..e1bd339e4e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs @@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc [InlineData(null, false)] [InlineData(true, false)] [InlineData(false, true)] - public void OnAuthorization_RedirectsToHttpsEndpoint_WithSpecifiedStatusCodeAndrequireHttpsPermanentOption(bool? permanent, bool requireHttpsPermanent) + public void OnAuthorization_RedirectsToHttpsEndpoint_WithSpecifiedStatusCodeAndRequireHttpsPermanentOption(bool? permanent, bool requireHttpsPermanent) { var requestContext = new DefaultHttpContext(); requestContext.RequestServices = CreateServices(null, requireHttpsPermanent); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs index 5aecf45a77..61b51de28f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing "testController", "testAction"); actionDescriptor.RouteValues.Add("randomKey", "testRandom"); - var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + var descriptorCollectionProvider = CreateActionDescriptorCollectionProvider(actionDescriptor); var services = new ServiceCollection(); services.AddRouting(); @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing "testAction"); actionDescriptor.RouteValues.Add("randomKey", "testRandom"); - var provider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + var provider = CreateActionDescriptorCollectionProvider(actionDescriptor); var constraint = new KnownRouteValueConstraint(provider); @@ -235,7 +235,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing private static HttpContext GetHttpContext(ActionDescriptor actionDescriptor, bool setupRequestServices = true) { - var descriptorCollectionProvider = CreateActionDesciprtorCollectionProvider(actionDescriptor); + var descriptorCollectionProvider = CreateActionDescriptorCollectionProvider(actionDescriptor); var context = new Mock(); if (setupRequestServices) @@ -247,7 +247,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return context.Object; } - private static IActionDescriptorCollectionProvider CreateActionDesciprtorCollectionProvider(ActionDescriptor actionDescriptor) + private static IActionDescriptorCollectionProvider CreateActionDescriptorCollectionProvider(ActionDescriptor actionDescriptor) { var actionProvider = new Mock(MockBehavior.Strict); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs index c6e00c231e..f6b212c7e5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs @@ -340,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing } [Fact] - public void Page_UsesValueFromRouteValueIfPageHandlerIsNotExplicitySpecified() + public void Page_UsesValueFromRouteValueIfPageHandlerIsNotExplicitlySpecified() { // Arrange UrlRouteContext actual = null; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs index 86d2f27071..ed4e80c7ff 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs @@ -1555,7 +1555,7 @@ namespace Microsoft.AspNetCore.Mvc var invoker = CreateInvoker( new IFilterMetadata[] { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter1.Object, // This filter should see the result returned from resourceFilter2 resourceFilter2.Object, // This filter will short circuit resourceFilter3.Object, // This shouldn't run - it will throw if it does exceptionFilter.Object, // This shouldn't run - it will throw if it does @@ -1603,7 +1603,7 @@ namespace Microsoft.AspNetCore.Mvc var invoker = CreateInvoker( new IFilterMetadata[] { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter1.Object, // This filter should see the result returned from resourceFilter2 resourceFilter2.Object, // This filter will short circuit resourceFilter3.Object, // This shouldn't run - it will throw if it does exceptionFilter.Object, // This shouldn't run - it will throw if it does @@ -1653,7 +1653,7 @@ namespace Microsoft.AspNetCore.Mvc var invoker = CreateInvoker( new IFilterMetadata[] { - resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter1.Object, // This filter should see the result returned from resourceFilter2 resourceFilter2.Object, resourceFilter3.Object, // This shouldn't run - it will throw if it does resultFilter.Object // This shouldn't run - it will throw if it does diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs index faff630e0b..2548dc26c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs @@ -181,7 +181,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { private List> _bindingActions = new List>(); private List> _displayActions = new List>(); - private List> _valiationActions = new List>(); + private List> _validationActions = new List>(); private readonly ModelMetadataIdentity _key; @@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding { if (_key.Equals(context.Key)) { - foreach (var action in _valiationActions) + foreach (var action in _validationActions) { action(context.ValidationMetadata); } @@ -237,7 +237,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding public IMetadataBuilder ValidationDetails(Action action) { - _valiationActions.Add(action); + _validationActions.Add(action); return this; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs index e3c9c4215b..ed140a72e2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs @@ -17,8 +17,8 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json public void OnProvidersExecuting_FindsJsonPatchDocuments_ProvidesOperationsArray() { // Arrange - var metadataprovider = new TestModelMetadataProvider(); - var provider = new JsonPatchOperationsArrayProvider(metadataprovider); + var metadataProvider = new TestModelMetadataProvider(); + var provider = new JsonPatchOperationsArrayProvider(metadataProvider); var jsonPatchParameterDescription = new ApiParameterDescription { Type = typeof(JsonPatchDocument) diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs index 59ac66a273..b89802115d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal [InlineData(typeof(List))] [InlineData(typeof(List))] [InlineData(typeof(PersonList))] - public void ThrowsArugmentExceptionFor_ConcreteEnumerableOfT(Type declaredType) + public void ThrowsArgumentExceptionFor_ConcreteEnumerableOfT(Type declaredType) { // Arrange var expectedMessage = "The type must be an interface and must be or derive from 'IEnumerable`1'."; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs index e300f1d434..e09a15b43c 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs @@ -13,10 +13,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal public void Creates_WrapperProvider_ForSerializableErrorType(bool isSerialization) { // Arrange - var serializableErroWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); + var serializableErrorWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); // Act - var wrapperProvider = serializableErroWrapperProviderFactory.GetProvider( + var wrapperProvider = serializableErrorWrapperProviderFactory.GetProvider( new WrapperProviderContext(typeof(SerializableError), isSerialization)); // Assert @@ -28,10 +28,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal public void ReturnsNullFor_NonSerializableErrorTypes() { // Arrange - var serializableErroWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); + var serializableErrorWrapperProviderFactory = new SerializableErrorWrapperProviderFactory(); // Act - var wrapperProvider = serializableErroWrapperProviderFactory.GetProvider( + var wrapperProvider = serializableErrorWrapperProviderFactory.GetProvider( new WrapperProviderContext(typeof(Person), isSerialization: true)); // Assert diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs index 885e231a5a..fdd09e318d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml } [Fact] - public void HasProperSuppportedMediaTypes() + public void HasProperSupportedMediaTypes() { // Arrange & Act var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); @@ -123,7 +123,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml } [Fact] - public void HasProperSuppportedEncodings() + public void HasProperSupportedEncodings() { // Arrange & Act var formatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs index 423edaaa20..c1bf95ba46 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs @@ -349,7 +349,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml } [Fact] - public void HasProperSuppportedMediaTypes() + public void HasProperSupportedMediaTypes() { // Arrange & Act var formatter = new XmlSerializerInputFormatter(new MvcOptions()); @@ -362,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml } [Fact] - public void HasProperSuppportedEncodings() + public void HasProperSupportedEncodings() { // Arrange & Act var formatter = new XmlSerializerInputFormatter(new MvcOptions()); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 6230542cb0..6b4b7acd73 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1359,7 +1359,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task ApiConvention_ForActionWtihApiConventionMethod() + public async Task ApiConvention_ForActionWithApiConventionMethod() { // Arrange var expectedMediaTypes = new[] { "application/json", "application/xml", "text/json", "text/xml" }; diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index fca64c9dc4..71502e6f24 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [Theory] [InlineData("application/json")] [InlineData("text/json")] - public async Task ActionLevelAttribute_OveridesClassLevel(string requestContentType) + public async Task ActionLevelAttribute_OverridesClassLevel(string requestContentType) { // Arrange var input = "{SampleString:\"" + requestContentType + "\"}"; diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs index a67fb5dd83..f90e239345 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests private const string EnumUrl = "http://localhost/Enum/Enum"; [Fact] - public async Task DataAnnotationLocalizionOfEnums_FromDataAnnotationLocalizerProvider() + public async Task DataAnnotationLocalizationOfEnums_FromDataAnnotationLocalizerProvider() { // Arrange & Act var response = await Client.GetAsync(EnumUrl); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs index 65ba3292dd..1f7e18760b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs @@ -404,7 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(expected2, response2.Trim()); // Act - 3 - // Resend the cookiesless request and cached result from the first response. + // Resend the cookieless request and cached result from the first response. var response3 = await Client.GetStringAsync("/catalog/cart?correlationid=3"); // Assert - 3 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 3c90af4882..8aca9a9bcf 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -1422,7 +1422,7 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPa } [Fact] - public async Task ViewDataAvaialableInPageFilter_AfterHandlerMethod_ReturnsPageResult() + public async Task ViewDataAwaitableInPageFilter_AfterHandlerMethod_ReturnsPageResult() { // Act var content = await Client.GetStringAsync("http://localhost/Pages/ViewDataAvailableAfterHandlerExecuted"); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 462e83326d..4518e7b442 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -384,7 +384,7 @@ Hello from /Pages/Shared/"; } [Fact] - public async Task AllowAnonymouseToPageConvention_CanBeAppliedToAreaPages() + public async Task AllowAnonymousToPageConvention_CanBeAppliedToAreaPages() { // Act var response = await Client.GetStringAsync("/Accounts/RequiresAuth/AllowAnonymous"); @@ -395,7 +395,7 @@ Hello from /Pages/Shared/"; // These test is important as it covers a feature that allows razor pages to use a different // model at runtime that wasn't known at compile time. Like a non-generic model used at compile - // time and overrided at runtime with a closed-generic model that performs the actual implementation. + // time and overriden at runtime with a closed-generic model that performs the actual implementation. // An example of this is how the Identity UI library defines a base page model in their views, // like how the Register.cshtml view defines its model as RegisterModel and then, at runtime it replaces // that model with RegisterModel where TUser is the type of the user used to configure identity. @@ -506,7 +506,7 @@ Hello from /Pages/Shared/"; } [Fact] - public async Task ViewDataAttributes_SetInPageModel_AreTransferedToLayout() + public async Task ViewDataAttributes_SetInPageModel_AreTransferredToLayout() { // Arrange var document = await Client.GetHtmlDocumentAsync("/ViewData/ViewDataInPage"); @@ -526,7 +526,7 @@ Hello from /Pages/Shared/"; } [Fact] - public async Task ViewDataAttributes_SetInPageWithoutModel_AreTransferedToLayout() + public async Task ViewDataAttributes_SetInPageWithoutModel_AreTransferredToLayout() { // Arrange var document = await Client.GetHtmlDocumentAsync("/ViewData/ViewDataInPageWithoutModel"); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 175dfaef1a..fbdb36f53d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -921,7 +921,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public virtual async Task AttributeRoutedAction_LinkWithName_WithNameOverrridenFromController() + public virtual async Task AttributeRoutedAction_LinkWithName_WithNameOverridenFromController() { // Arrange & Act var response = await Client.DeleteAsync("http://localhost/api/Company/5"); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs index b5169c96ab..214fcc0eb5 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } [Fact] - public async Task JsonSerializeFormated() + public async Task JsonSerializeFormatted() { // Arrange var expected = "{" + Environment.NewLine diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs index 9c807942ea..e6559100f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs @@ -89,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task ReregisteringAntiforgeryTokenInsideFormTagHelper_DoesNotAddDuplicateAntiforgeryTokenFields() + public async Task ReRegisteringAntiforgeryTokenInsideFormTagHelper_DoesNotAddDuplicateAntiforgeryTokenFields() { // Arrange var expectedMediaType = MediaTypeHeaderValue.Parse("text/html; charset=utf-8"); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs index 9080f544e9..054b28d7cb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } [Fact] - public async Task ActionThrowsHttpResponseException_EnsureGlobalHttpresponseExceptionActionFilter_IsInvoked() + public async Task ActionThrowsHttpResponseException_EnsureGlobalHttpResponseExceptionActionFilter_IsInvoked() { // Arrange & Act var response = await Client.GetAsync( diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs index 3b97473fdd..1b1b74a591 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Verifies that the model state has errors related to body model validation. [Fact] - public async Task DataMissingForRefereneceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors() + public async Task DataMissingForReferenceTypeProperties_AndModelIsBound_AndHasMixedValidationErrors() { // Arrange var input = "(); @@ -2040,8 +2040,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor routeData.Values.Add(kvp.Key, kvp.Value); } - var actionDesciptor = new ActionDescriptor(); - return new ActionContext(httpContext, routeData, actionDesciptor); + var actionDescriptor = new ActionDescriptor(); + return new ActionContext(httpContext, routeData, actionDescriptor); } private static ActionContext GetActionContextWithActionDescriptor( diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs index 85b51820df..c15912dede 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs @@ -20,12 +20,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { // Arrange var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())); - var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); + var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); var typeInfo = typeof(PageWithAuthorizeHandlers).GetTypeInfo(); var context = GetApplicationProviderContext(typeInfo); // Act - autorizationProvider.OnProvidersExecuting(context); + authorizationProvider.OnProvidersExecuting(context); // Assert Assert.Collection( @@ -53,11 +53,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { // Arrange var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())); - var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); + var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); var context = GetApplicationProviderContext(typeof(TestPage).GetTypeInfo()); // Act - autorizationProvider.OnProvidersExecuting(context); + authorizationProvider.OnProvidersExecuting(context); // Assert Assert.Collection( @@ -90,12 +90,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal options.Value.AddPolicy("Derived", policy => policy.RequireClaim("Derived")); var policyProvider = new DefaultAuthorizationPolicyProvider(options); - var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); + var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); var context = GetApplicationProviderContext(typeof(TestPageWithDerivedModel).GetTypeInfo()); // Act - autorizationProvider.OnProvidersExecuting(context); + authorizationProvider.OnProvidersExecuting(context); // Assert AuthorizeFilter authorizeFilter = null; @@ -133,11 +133,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { // Arrange var policyProvider = new DefaultAuthorizationPolicyProvider(Options.Create(new AuthorizationOptions())); - var autorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); + var authorizationProvider = new AuthorizationPageApplicationModelProvider(policyProvider); var context = GetApplicationProviderContext(typeof(PageWithAnonymousModel).GetTypeInfo()); // Act - autorizationProvider.OnProvidersExecuting(context); + authorizationProvider.OnProvidersExecuting(context); // Assert Assert.Collection( diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs index 7d12648d04..4627d28c89 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs @@ -251,7 +251,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } [Fact] - public void CreateHandlerMethods_CopiesParameterDecriptorsFromParameterModel() + public void CreateHandlerMethods_CopiesParameterDescriptorsFromParameterModel() { // Arrange var actionDescriptor = new PageActionDescriptor(); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs index fa00d8bd60..1e2ced5c55 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs @@ -520,7 +520,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnProvidersExecuting_UsesTheFirstDescriptorForEachPath() { // ViewsFeature may contain duplicate entries for the same Page - for instance when an app overloads a library's views. - // It picks the first entry for each path. In the ordinary case, this should ensure that the app's Razor Pages are prefered + // It picks the first entry for each path. In the ordinary case, this should ensure that the app's Razor Pages are preferred // to a Razor Page added by a library. // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs index 1a82c834ec..2ad90a08a8 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs @@ -455,7 +455,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.PageType); } - // We want to test the 'empty' page and pagemodel has no bound properties, and no handler methods. + // We want to test the 'empty' page and PageModel has no bound properties, and no handler methods. [Fact] public void OnProvidersExecuting_EmptyPageModel() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs index 46b7996080..bbe92dd6da 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } [Fact] - public async Task OnResultExecutionAsyn_ExecutesSyncFilters() + public async Task OnResultExecutionAsync_ExecutesSyncFilters() { // Arrange var pageContext = new PageContext(new ActionContext( diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs index 3decf16891..bfb8d6991c 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs @@ -1021,10 +1021,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages // Arrange var pageModel = new TestPageModel(); var pageName = "/Page-Name"; - var routeVaues = new { key = "value" }; + var routeValues = new { key = "value" }; // Act - var result = pageModel.RedirectToPage(pageName, routeVaues); + var result = pageModel.RedirectToPage(pageName, routeValues); // Assert Assert.IsType(result); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs index 2e4e50cf91..3a0e7ad2b1 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs @@ -1096,10 +1096,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages // Arrange var page = new TestPage(); var pageName = "/Page-Name"; - var routeVaues = new { key = "value" }; + var routeValues = new { key = "value" }; // Act - var result = page.RedirectToPage(pageName, routeVaues); + var result = page.RedirectToPage(pageName, routeValues); // Assert Assert.IsType(result); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs index f913e6a59b..b3787dccec 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs @@ -146,10 +146,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers for (var i = 0; i < expectedOutput.Attributes.Count; i++) { - var expectedAtribute = expectedOutput.Attributes[i]; + var expectedAttribute = expectedOutput.Attributes[i]; var actualAttribute = output.Attributes[i]; - Assert.Equal(expectedAtribute.Name, actualAttribute.Name); - Assert.Equal(expectedAtribute.Value.ToString(), actualAttribute.Value.ToString()); + Assert.Equal(expectedAttribute.Name, actualAttribute.Name); + Assert.Equal(expectedAttribute.Value.ToString(), actualAttribute.Value.ToString()); } } diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs index a4aff7f9ab..2fc93f6c91 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs @@ -65,7 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal } [Fact] - public void DetermineMode_SetsModeWithHigestValue() + public void DetermineMode_SetsModeWithHighestValue() { // Arrange var modeInfos = new[] diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index f2700f2a97..b4c06b2613 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Mvc services.AddSingleton(GetHostingEnvironment()); // Register a mock implementation of each service, AddMvcServices should add another implementation. - foreach (var serviceType in MutliRegistrationServiceTypes) + foreach (var serviceType in MultiRegistrationServiceTypes) { var mockType = typeof(Mock<>).MakeGenericType(serviceType.Key); services.Add(ServiceDescriptor.Transient(serviceType.Key, mockType)); @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc services.AddMvc(); // Assert - foreach (var serviceType in MutliRegistrationServiceTypes) + foreach (var serviceType in MultiRegistrationServiceTypes) { AssertServiceCountEquals(services, serviceType.Key, serviceType.Value.Length + 1); @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Mvc } [Fact] - public void AddMvcTwice_DoesNotAddDuplicateFramewokrParts() + public void AddMvcTwice_DoesNotAddDuplicateFrameworkParts() { // Arrange var mvcRazorAssembly = typeof(UrlResolutionTagHelper).GetTypeInfo().Assembly; @@ -323,7 +323,7 @@ namespace Microsoft.AspNetCore.Mvc services.AddSingleton(GetHostingEnvironment()); services.AddMvc(); - var multiRegistrationServiceTypes = MutliRegistrationServiceTypes; + var multiRegistrationServiceTypes = MultiRegistrationServiceTypes; return services .Where(sd => !multiRegistrationServiceTypes.Keys.Contains(sd.ServiceType)) .Where(sd => sd.ServiceType.GetTypeInfo().Assembly.FullName.Contains("Mvc")) @@ -331,7 +331,7 @@ namespace Microsoft.AspNetCore.Mvc } } - private Dictionary MutliRegistrationServiceTypes + private Dictionary MultiRegistrationServiceTypes { get { diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs index 6abab5d7a5..07567c78b9 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs @@ -439,7 +439,7 @@ namespace Microsoft.AspNetCore.Mvc.Test var viewData = new ViewDataDictionary(metadataProvider, new ModelStateDictionary()); var tempData = new TempDataDictionary(httpContext, Mock.Of()); - var valiatorProviders = new[] + var validatorProviders = new[] { new DataAnnotationsModelValidatorProvider( new ValidationAttributeAdapterProvider(), @@ -458,7 +458,7 @@ namespace Microsoft.AspNetCore.Mvc.Test { ControllerContext = controllerContext, MetadataProvider = metadataProvider, - ObjectValidator = new DefaultObjectValidator(metadataProvider, valiatorProviders, new MvcOptions()), + ObjectValidator = new DefaultObjectValidator(metadataProvider, validatorProviders, new MvcOptions()), TempData = tempData, ViewData = viewData, }; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs index eee3b4fb66..f316eea6be 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs @@ -393,7 +393,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal [Theory] [MemberData(nameof(IndexerExpressions))] [MemberData(nameof(UnsupportedExpressions))] - public void GetExpressionText_DoesNotCacheIndexerOrUnspportedExpression(LambdaExpression expression) + public void GetExpressionText_DoesNotCacheIndexerOrUnsupportedExpression(LambdaExpression expression) { // Act - 1 var text1 = ExpressionHelper.GetExpressionText(expression, _expressionTextCache); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs index 44d421c376..c47802cfd0 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } [Fact] - public void FromLambaExpression_SetsContainerAsExpected() + public void FromLambdaExpression_SetsContainerAsExpected() { // Arrange var myModel = new TestModel { SelectedCategory = new Category() }; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs index 164b899151..eca38d7dc2 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs @@ -186,12 +186,12 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures Assert.NotEqual(cachedKeyHashCode1, hashCode2); } - private static MemberExpressionCacheKey GetKey(Expression> expresssion) - => GetKey(expresssion); + private static MemberExpressionCacheKey GetKey(Expression> expression) + => GetKey(expression); - private static MemberExpressionCacheKey GetKey(Expression> expresssion) + private static MemberExpressionCacheKey GetKey(Expression> expression) { - var memberExpression = Assert.IsAssignableFrom(expresssion.Body); + var memberExpression = Assert.IsAssignableFrom(expression.Body); return new MemberExpressionCacheKey(typeof(TModel), memberExpression); } diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs index 5de4c308c3..eb4cebb948 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs @@ -70,9 +70,9 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures Assert.Equal(expected, actual); } - private static MemberExpressionCacheKey GetKey(Expression> expresssion) + private static MemberExpressionCacheKey GetKey(Expression> expression) { - var memberExpression = Assert.IsAssignableFrom(expresssion.Body); + var memberExpression = Assert.IsAssignableFrom(expression.Body); return new MemberExpressionCacheKey(typeof(TestModel), memberExpression); } diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs index 13beb13337..0624f2aa95 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs @@ -708,7 +708,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering [Theory] [MemberData(nameof(ActionNameControllerNameRouteValuesAndMethodDataSet))] - public void BeginFormWithActionNameContollerNameRouteValuesAndMethodParameters_CallsHtmlGeneratorWithExpectedValues( + public void BeginFormWithActionNameControllerNameRouteValuesAndMethodParameters_CallsHtmlGeneratorWithExpectedValues( string actionName, string controllerName, object routeValues, @@ -754,7 +754,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering [Theory] [MemberData(nameof(ActionNameControllerNameMethodAndHtmlAttributesDataSet))] - public void BeginFormWithActionNameContollerNameMethodAndHtmlAttributesParameters_CallsHtmlGeneratorWithExpectedValues( + public void BeginFormWithActionNameControllerNameMethodAndHtmlAttributesParameters_CallsHtmlGeneratorWithExpectedValues( string actionName, string controllerName, FormMethod method, @@ -800,7 +800,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering [Theory] [MemberData(nameof(ActionNameControllerNameMethodAndHtmlAttributesDataSet))] - public void BeginFormWithActionNameContollerNameMethodAndHtmlAttributesParameters_WithAntiforgery_CallsHtmlGeneratorWithExpectedValues( + public void BeginFormWithActionNameControllerNameMethodAndHtmlAttributesParameters_WithAntiforgery_CallsHtmlGeneratorWithExpectedValues( string actionName, string controllerName, FormMethod method, @@ -848,7 +848,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering [Theory] [MemberData(nameof(ActionNameControllerNameMethodAndHtmlAttributesDataSet))] - public void BeginFormWithActionNameContollerNameMethodAndHtmlAttributesParameters_SuppressAntiforgery_CallsHtmlGeneratorWithExpectedValues( + public void BeginFormWithActionNameControllerNameMethodAndHtmlAttributesParameters_SuppressAntiforgery_CallsHtmlGeneratorWithExpectedValues( string actionName, string controllerName, FormMethod method, diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs index 56ddb2c7a2..41050775f2 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs @@ -1037,7 +1037,7 @@ namespace Microsoft.AspNetCore.Mvc.Rendering } [Fact] - public void ListBoxFor_WithUnreleatedExpression_GeneratesExpectedValue() + public void ListBoxFor_WithUnrelatedExpression_GeneratesExpectedValue() { // Arrange var unrelated = new[] { "2" }; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs index 31f0d036d6..2a2a355257 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public class ViewDataDictionaryOfTModelTest { [Fact] - public void Constructor_InitalizesMembers() + public void Constructor_InitializesMembers() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } [Fact] - public void CopyConstructors_InitalizeModelAndModelMetadataBasedOnSource() + public void CopyConstructors_InitializeModelAndModelMetadataBasedOnSource() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); @@ -73,7 +73,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } [Fact] - public void CopyConstructors_InitalizeModelAndModelMetadataBasedOnSource_NullModel() + public void CopyConstructors_InitializeModelAndModelMetadataBasedOnSource_NullModel() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } [Fact] - public void CopyConstructor_InitalizesModelAndModelMetadataBasedOnSource_ModelOfSubclass() + public void CopyConstructor_InitializesModelAndModelMetadataBasedOnSource_ModelOfSubclass() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs index cfcb4bf426..bd2b2e47ba 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures public class ViewDataDictionaryTest { [Fact] - public void ConstructorWithOneParameterInitalizesMembers() + public void ConstructorWithOneParameterInitializesMembers() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } [Fact] - public void ConstructorInitalizesMembers() + public void ConstructorInitializesMembers() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); @@ -186,7 +186,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures } [Fact] - public void CopyConstructorInitalizesModelAndModelMetadataBasedOnSource() + public void CopyConstructorInitializesModelAndModelMetadataBasedOnSource() { // Arrange var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs index f55d3a91fd..7fc8949fa3 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs @@ -140,11 +140,11 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures // Arrange var tempDataNull = false; var viewDataNull = false; - var deligateHit = false; + var delegateHit = false; var view = CreateView(async (v) => { - deligateHit = true; + delegateHit = true; tempDataNull = v.TempData == null; viewDataNull = v.ViewData == null; @@ -174,7 +174,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures // Assert Assert.Equal(200, context.Response.StatusCode); - Assert.True(deligateHit); + Assert.True(delegateHit); Assert.False(viewDataNull); Assert.False(tempDataNull); } diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs index 33a5897181..0f891bb34a 100644 --- a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs +++ b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs @@ -185,14 +185,14 @@ namespace Microsoft.TestCommon private static IEnumerable GetDataSetFromTestDataCollection(IEnumerable testDataCollection, TestDataVariations variations) { - foreach (TestData testdataInstance in testDataCollection) + foreach (TestData testDataInstance in testDataCollection) { - foreach (TestDataVariations variation in testdataInstance.GetSupportedTestDataVariations()) + foreach (TestDataVariations variation in testDataInstance.GetSupportedTestDataVariations()) { if ((variation & variations) == variation) { - Type variationType = testdataInstance.GetAsTypeOrNull(variation); - object testData = testdataInstance.GetAsTestDataOrNull(variation); + Type variationType = testDataInstance.GetAsTypeOrNull(variation); + object testData = testDataInstance.GetAsTestDataOrNull(variation); if (AsSingleInstances(variation)) { foreach (object obj in (IEnumerable)testData) diff --git a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index 13942b0046..f611c8d421 100644 --- a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { public class ApiConventionAnalyzerIntegrationTest { - private MvcDiagnosticAnalyzerRunner Executor { get; } = new ApiCoventionWith1006DiagnosticEnabledRunner(); + private MvcDiagnosticAnalyzerRunner Executor { get; } = new ApiConventionWith1006DiagnosticEnabledRunner(); [Fact] public Task NoDiagnosticsAreReturned_ForNonApiController() @@ -129,9 +129,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers Assert.Equal(string.Format(descriptor.MessageFormat.ToString(), args), diagnostic.GetMessage()); }); } - private class ApiCoventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner + private class ApiConventionWith1006DiagnosticEnabledRunner : MvcDiagnosticAnalyzerRunner { - public ApiCoventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer()) + public ApiConventionWith1006DiagnosticEnabledRunner() : base(new ApiConventionAnalyzer()) { } diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs index 041d148624..864a6b3791 100644 --- a/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs @@ -159,7 +159,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrfix() + public void IsNameMatch_WithPrefix_ReturnsFalse_IfNameIsNotProperPrefix() { // Arrange var name = "Postman"; @@ -303,7 +303,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public async Task IsTypeMatch_WithAssinableFrom_ReturnsTrueForDerived() + public async Task IsTypeMatch_WithAssignableFrom_ReturnsTrueForDerived() { // Arrange var compilation = await GetCompilationAsync(); @@ -320,7 +320,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public async Task IsTypeMatch_WithAssinableFrom_ReturnsFalseForBaseTypes() + public async Task IsTypeMatch_WithAssignableFrom_ReturnsFalseForBaseTypes() { // Arrange var compilation = await GetCompilationAsync(); @@ -336,7 +336,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public async Task IsTypeMatch_WithAssinableFrom_ReturnsFalseForUnrelated() + public async Task IsTypeMatch_WithAssignableFrom_ReturnsFalseForUnrelated() { // Arrange var compilation = await GetCompilationAsync(); @@ -396,7 +396,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public Task IsMatch_ReturnsTrue_IfMethodNameAndParametersMatchs() + public Task IsMatch_ReturnsTrue_IfMethodNameAndParametersMatches() { // Arrange var methodName = nameof(TestController.Get); diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index e6ccc12470..483901ffa1 100644 --- a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -258,7 +258,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task GetResponseMetadata_IgnoresAttributesWithIncorrectStatusCodeType() { - return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues( + return GetResponseMetadata_WorksForInvalidOrUnsupportedAttributes( nameof(GetResponseMetadata_ControllerActionWithAttributes), nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithProducesResponseTypeWithIncorrectStatusCodeType)); } @@ -266,12 +266,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task GetResponseMetadata_IgnoresDerivedAttributesWithoutPropertyOnConstructorArguments() { - return GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues( + return GetResponseMetadata_WorksForInvalidOrUnsupportedAttributes( nameof(GetResponseMetadata_ControllerActionWithAttributes), nameof(GetResponseMetadata_ControllerActionWithAttributes.ActionWithCustomProducesResponseTypeAttributeWithoutArguments)); } - private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttribues(string typeName, string methodName) + private async Task GetResponseMetadata_WorksForInvalidOrUnsupportedAttributes(string typeName, string methodName) { // Arrange var compilation = await GetResponseMetadataCompilation(); From 07cc9e66c6d936cbf5f8ddf1d37fa625ab3fb365 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 6 Sep 2018 10:16:31 -0700 Subject: [PATCH 223/316] Add a feature to disable file watching in Razor pages (#8369) * Add a feature to disable file watching in Razor pages Fixes https://github.com/aspnet/Mvc/issues/8362 --- ...faultActionDescriptorCollectionProvider.cs | 1 - .../MvcRazorMvcCoreBuilderExtensions.cs | 3 + .../Internal/RazorViewCompiler.cs | 69 ++++++--- .../Internal/RazorViewCompilerProvider.cs | 5 +- .../Properties/AssemblyInfo.cs | 1 + .../RazorViewEngineOptions.cs | 62 ++++++++- .../RazorViewEngineOptionsSetup.cs | 43 ++++-- .../PageActionDescriptorChangeProvider.cs | 17 ++- .../RazorFileUpdateTests.cs | 131 ++++++++++++++++++ .../ViewEngineTests.cs | 22 --- .../Internal/RazorViewCompilerTest.cs | 106 +++++++++++++- .../RazorViewEngineOptionsSetupTest.cs | 130 ++++++++++++++++- .../RazorViewEngineTest.cs | 6 +- .../PageActionDescriptorChangeProviderTest.cs | 42 +++++- ...zorPagesRazorViewEngineOptionsSetupTest.cs | 8 +- .../CompatibilitySwitchIntegrationTest.cs | 12 ++ .../NonWatchingPhysicalFileProvider.cs | 18 +++ .../RazorPagesWebSite/StartupWithBasePath.cs | 18 ++- .../UpdateableFileProviderController.cs | 11 +- .../Services/UpdateableFileProvider.cs | 59 +++++++- 20 files changed, 685 insertions(+), 79 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Razor/{Internal => }/RazorViewEngineOptionsSetup.cs (50%) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs create mode 100644 test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs index 9dce3d6f5a..aef4292b6c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Diagnostics; diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 4960f18b41..7ede8232f4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -159,6 +159,9 @@ namespace Microsoft.Extensions.DependencyInjection services.TryAddEnumerable( ServiceDescriptor.Transient, RazorViewEngineOptionsSetup>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, RazorViewEngineOptionsSetup>()); + services.TryAddSingleton< IRazorViewEngineFileProviderAccessor, DefaultRazorViewEngineFileProviderAccessor>(); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs index 3d107018c2..9bbce19d88 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs @@ -116,6 +116,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal } } + public bool AllowRecompilingViewsOnFileChange { get; set; } + /// public Task CompileAsync(string relativePath) { @@ -254,16 +256,9 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal // Used to validate and recompile NormalizedPath = normalizedPath, - ExpirationTokens = new List(), - }; - var checksums = precompiledView.Item.GetChecksumMetadata(); - for (var i = 0; i < checksums.Count; i++) - { - // We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job, - // so it probably will. - item.ExpirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier)); - } + ExpirationTokens = GetExpirationTokens(precompiledView), + }; // We also need to create a new descriptor, because the original one doesn't have expiration tokens on // it. These will be used by the view location cache, which is like an L1 cache for views (this class is @@ -282,10 +277,13 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath) { - var expirationTokens = new List() + IList expirationTokens = Array.Empty(); + + if (AllowRecompilingViewsOnFileChange) { - _fileProvider.Watch(normalizedPath), - }; + var changeToken = _fileProvider.Watch(normalizedPath); + expirationTokens = new List { changeToken }; + } var projectItem = _projectEngine.FileSystem.GetItem(normalizedPath); if (!projectItem.Exists) @@ -313,9 +311,46 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal _logger.ViewCompilerFoundFileToCompile(normalizedPath); + GetChangeTokensFromImports(expirationTokens, projectItem); + + return new ViewCompilerWorkItem() + { + SupportsCompilation = true, + + NormalizedPath = normalizedPath, + ExpirationTokens = expirationTokens, + }; + } + + private IList GetExpirationTokens(CompiledViewDescriptor precompiledView) + { + if (!AllowRecompilingViewsOnFileChange) + { + return Array.Empty(); + } + + var checksums = precompiledView.Item.GetChecksumMetadata(); + var expirationTokens = new List(checksums.Count); + + for (var i = 0; i < checksums.Count; i++) + { + // We rely on Razor to provide the right set of checksums. Trust the compiler, it has to do a good job, + // so it probably will. + expirationTokens.Add(_fileProvider.Watch(checksums[i].Identifier)); + } + + return expirationTokens; + } + + private void GetChangeTokensFromImports(IList expirationTokens, RazorProjectItem projectItem) + { + if (!AllowRecompilingViewsOnFileChange) + { + return; + } + // OK this means we can do compilation. For now let's just identify the other files we need to watch // so we can create the cache entry. Compilation will happen after we release the lock. - var importFeature = _projectEngine.ProjectFeatures.OfType().FirstOrDefault(); // There should always be an import feature unless someone has misconfigured their RazorProjectEngine. @@ -330,14 +365,6 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal { expirationTokens.Add(_fileProvider.Watch(physicalImport.FilePath)); } - - return new ViewCompilerWorkItem() - { - SupportsCompilation = true, - - NormalizedPath = normalizedPath, - ExpirationTokens = expirationTokens, - }; } protected virtual CompiledViewDescriptor CompileAndEmit(string relativePath) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs index 560b1493d6..3776bc845f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs @@ -80,7 +80,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal #pragma warning restore CS0618 // Type or member is obsolete feature.ViewDescriptors, _compilationMemoryCacheProvider.CompilationMemoryCache, - _logger); + _logger) + { + AllowRecompilingViewsOnFileChange = _viewEngineOptions.AllowRecompilingViewsOnFileChange, + }; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs index 5bd324d5d6..73c4c5a356 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs @@ -7,3 +7,4 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Razor.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs index 8ae0120211..6c6148cb24 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs @@ -2,8 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.CodeAnalysis; using Microsoft.Extensions.FileProviders; @@ -13,10 +15,21 @@ namespace Microsoft.AspNetCore.Mvc.Razor /// /// Provides programmatic configuration for the . /// - public class RazorViewEngineOptions + public class RazorViewEngineOptions : IEnumerable { + private readonly ICompatibilitySwitch[] _switches; + private readonly CompatibilitySwitch _allowRecompilingViewsOnFileChange; private Action _compilationCallback = c => { }; + public RazorViewEngineOptions() + { + _allowRecompilingViewsOnFileChange = new CompatibilitySwitch(nameof(AllowRecompilingViewsOnFileChange)); + _switches = new[] + { + _allowRecompilingViewsOnFileChange, + }; + } + /// /// Gets a used by the . /// @@ -181,5 +194,52 @@ namespace Microsoft.AspNetCore.Mvc.Razor _compilationCallback = value; } } + + /// + /// Gets or sets a value that determines if Razor files (Razor Views and Razor Pages) are recompiled and updated + /// if files change on disk. + /// + /// When , MVC will use to watch for changes to + /// Razor files in configured instances. + /// + /// + /// + /// The default value is if the version is + /// or earlier. If the version is later and is Development, + /// the default value is . Otherwise, the default value is . + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless + /// is Development or the value is explicitly configured. + /// + /// + public bool AllowRecompilingViewsOnFileChange + { + // Note: When compatibility switches are removed in 3.0, this property should be retained as a regular boolean property. + get => _allowRecompilingViewsOnFileChange.Value; + set => _allowRecompilingViewsOnFileChange.Value = value; + } + + IEnumerator IEnumerable.GetEnumerator() + { + return ((IEnumerable)_switches).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => _switches.GetEnumerator(); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs similarity index 50% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.cs rename to src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs index e8c49113ba..6747d87726 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewEngineOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs @@ -2,27 +2,45 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.Mvc.Razor.Internal +namespace Microsoft.AspNetCore.Mvc.Razor { - /// - /// Sets up default options for . - /// - public class RazorViewEngineOptionsSetup : IConfigureOptions + internal class RazorViewEngineOptionsSetup : + ConfigureCompatibilityOptions, + IConfigureOptions { private readonly IHostingEnvironment _hostingEnvironment; - /// - /// Initializes a new instance of . - /// - /// for the application. - public RazorViewEngineOptionsSetup(IHostingEnvironment hostingEnvironment) + public RazorViewEngineOptionsSetup( + IHostingEnvironment hostingEnvironment, + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) { _hostingEnvironment = hostingEnvironment ?? throw new ArgumentNullException(nameof(hostingEnvironment)); } + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + if (Version < CompatibilityVersion.Version_2_2) + { + // Default to true in 2.1 or earlier. In 2.2, we have to conditionally enable this + // and consequently this switch has no default value. + values[nameof(RazorViewEngineOptions.AllowRecompilingViewsOnFileChange)] = true; + } + + return values; + } + } + public void Configure(RazorViewEngineOptions options) { if (options == null) @@ -41,6 +59,11 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal options.AreaViewLocationFormats.Add("/Areas/{2}/Views/{1}/{0}" + RazorViewEngine.ViewExtension); options.AreaViewLocationFormats.Add("/Areas/{2}/Views/Shared/{0}" + RazorViewEngine.ViewExtension); options.AreaViewLocationFormats.Add("/Views/Shared/{0}" + RazorViewEngine.ViewExtension); + + if (_hostingEnvironment.IsDevelopment()) + { + options.AllowRecompilingViewsOnFileChange = true; + } } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs index c68ee3ed65..fadb391b90 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs @@ -5,6 +5,7 @@ using System; using System.Diagnostics; using System.Linq; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Razor.Language; using Microsoft.Extensions.FileProviders; @@ -18,11 +19,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private readonly IFileProvider _fileProvider; private readonly string[] _searchPatterns; private readonly string[] _additionalFilesToTrack; + private readonly bool _watchForChanges; public PageActionDescriptorChangeProvider( RazorTemplateEngine templateEngine, IRazorViewEngineFileProviderAccessor fileProviderAccessor, - IOptions razorPagesOptions) + IOptions razorPagesOptions, + IOptions razorViewEngineOptions) { if (templateEngine == null) { @@ -39,6 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal throw new ArgumentNullException(nameof(razorPagesOptions)); } + _watchForChanges = razorViewEngineOptions.Value.AllowRecompilingViewsOnFileChange; + if (!_watchForChanges) + { + // No need to do any additional work if we aren't going to be watching for file changes. + return; + } + _fileProvider = fileProviderAccessor.FileProvider; var rootDirectory = razorPagesOptions.Value.RootDirectory; @@ -84,6 +94,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public IChangeToken GetChangeToken() { + if (!_watchForChanges) + { + return NullChangeToken.Singleton; + } + var changeTokens = new IChangeToken[_additionalFilesToTrack.Length + _searchPatterns.Length]; for (var i = 0; i < _additionalFilesToTrack.Length; i++) { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs new file mode 100644 index 0000000000..b4aee9ad9c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + // Verifies that updating Razor files (views and pages) with AllowRecompilingViewsOnFileChange=true works + public class RazorFileUpdateTests : IClassFixture> + { + public RazorFileUpdateTests(MvcTestFixture fixture) + { + var factory = fixture.WithWebHostBuilder(builder => + { + builder.UseStartup(); + builder.ConfigureTestServices(services => + { + services.Configure(options => options.AllowRecompilingViewsOnFileChange = true); + }); + }); + Client = factory.CreateDefaultClient(); + } + + public HttpClient Client { get; } + + [Fact] + public async Task RazorViews_AreUpdatedOnChange() + { + // Arrange + var expected1 = "Original content"; + var expected2 = "New content"; + var path = "/Views/UpdateableShared/_Partial.cshtml"; + + // Act - 1 + var body = await Client.GetStringAsync("/UpdateableFileProvider"); + + // Assert - 1 + Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true); + + // Act - 2 + await UpdateFile(path, expected2); + body = await Client.GetStringAsync("/UpdateableFileProvider"); + + // Assert - 2 + Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true); + } + + [Fact] + public async Task RazorViews_AreUpdatedWhenViewImportsChange() + { + // Arrange + var content = "@GetType().Assembly.FullName"; + await UpdateFile("/Views/UpdateableIndex/Index.cshtml", content); + var initial = await Client.GetStringAsync("/UpdateableFileProvider"); + + // Act + // Trigger a change in ViewImports + await UpdateFile("/Views/UpdateableIndex/_ViewImports.cshtml", string.Empty); + var updated = await Client.GetStringAsync("/UpdateableFileProvider"); + + // Assert + Assert.NotEqual(initial, updated); + } + + [Fact] + public async Task RazorPages_AreUpdatedOnChange() + { + // Arrange + var expected1 = "Original content"; + var expected2 = "New content"; + + // Act - 1 + var body = await Client.GetStringAsync("/UpdateablePage"); + + // Assert - 1 + Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true); + + // Act - 2 + await UpdateRazorPages(); + await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + expected2); + body = await Client.GetStringAsync("/UpdateablePage"); + + // Assert - 2 + Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true); + } + + [Fact] + public async Task RazorPages_AreUpdatedWhenViewImportsChange() + { + // Arrange + var content = "@GetType().Assembly.FullName"; + await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content); + var initial = await Client.GetStringAsync("/UpdateablePage"); + + // Act + // Trigger a change in ViewImports + await UpdateRazorPages(); + await UpdateFile("/Pages/UpdateablePage.cshtml", "@page" + Environment.NewLine + content); + var updated = await Client.GetStringAsync("/UpdateablePage"); + + // Assert + Assert.NotEqual(initial, updated); + } + + private async Task UpdateFile(string path, string content) + { + var updateContent = new FormUrlEncodedContent(new Dictionary + { + { "path", path }, + { "content", content }, + }); + + var response = await Client.PostAsync($"/UpdateableFileProvider/Update", updateContent); + response.EnsureSuccessStatusCode(); + } + + private async Task UpdateRazorPages() + { + var response = await Client.PostAsync($"/UpdateableFileProvider/UpdateRazorPages", new StringContent(string.Empty)); + response.EnsureSuccessStatusCode(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs index 363f534469..7c23e4c377 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs @@ -270,28 +270,6 @@ Hello from Shared/_EmbeddedPartial Assert.Equal(expected, body.Trim(), ignoreLineEndingDifferences: true); } - [Fact] - public async Task RazorViewEngine_UpdatesViewsReferencedViaRelativePathsOnChange() - { - // Arrange - var expected1 = "Original content"; - var expected2 = "New content"; - - // Act - 1 - var body = await Client.GetStringAsync("/UpdateableFileProvider"); - - // Assert - 1 - Assert.Equal(expected1, body.Trim(), ignoreLineEndingDifferences: true); - - // Act - 2 - var response = await Client.PostAsync("/UpdateableFileProvider/Update", new StringContent(string.Empty)); - response.EnsureSuccessStatusCode(); - body = await Client.GetStringAsync("/UpdateableFileProvider"); - - // Assert - 1 - Assert.Equal(expected2, body.Trim(), ignoreLineEndingDifferences: true); - } - [Fact] public async Task LayoutValueIsPassedBetweenNestedViewStarts() { diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs index d92c4ef4a1..ce99154a00 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs @@ -37,6 +37,25 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var result1 = await viewCompiler.CompileAsync(path); var result2 = await viewCompiler.CompileAsync(path); + // Assert + Assert.Same(result1, result2); + Assert.Null(result1.ViewAttribute); + Assert.Empty(result1.ExpirationTokens); + } + + [Fact] + public async Task CompileAsync_ReturnsResultWithExpirationToken_WhenWatchingForFileChanges() + { + // Arrange + var path = "/file/does-not-exist"; + var fileProvider = new TestFileProvider(); + var viewCompiler = GetViewCompiler(fileProvider); + viewCompiler.AllowRecompilingViewsOnFileChange = true; + + // Act + var result1 = await viewCompiler.CompileAsync(path); + var result2 = await viewCompiler.CompileAsync(path); + // Assert Assert.Same(result1, result2); Assert.Null(result1.ViewAttribute); @@ -57,6 +76,24 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal // Act var result = await viewCompiler.CompileAsync(path); + // Assert + Assert.NotNull(result.ViewAttribute); + Assert.Empty(result.ExpirationTokens); + } + + [Fact] + public async Task CompileAsync_AddsChangeTokensForViewStartsIfFileExists_WhenWatchingForFileChanges() + { + // Arrange + var path = "/file/exists/FilePath.cshtml"; + var fileProvider = new TestFileProvider(); + fileProvider.AddFile(path, "Content"); + var viewCompiler = GetViewCompiler(fileProvider); + viewCompiler.AllowRecompilingViewsOnFileChange = true; + + // Act + var result = await viewCompiler.CompileAsync(path); + // Assert Assert.NotNull(result.ViewAttribute); Assert.Collection( @@ -92,13 +129,40 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal } [Fact] - public async Task CompileAsync_InvalidatesCache_IfChangeTokenExpires() + public async Task CompileAsync_DoesNotInvalidCache_IfChangeTokenChanges() { // Arrange var path = "/Views/Home/Index.cshtml"; var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); + var changeToken = fileProvider.Watch(path); + + // Act 1 + var result1 = await viewCompiler.CompileAsync(path); + + // Assert 1 + Assert.NotNull(result1.ViewAttribute); + + // Act 2 + fileProvider.DeleteFile(path); + fileProvider.GetChangeToken(path).HasChanged = true; + viewCompiler.Compile = _ => throw new Exception("Can't call me"); + var result2 = await viewCompiler.CompileAsync(path); + + // Assert 2 + Assert.Same(result1, result2); + } + + [Fact] + public async Task CompileAsync_InvalidatesCache_IfChangeTokenExpires_WhenWatchingForFileChanges() + { + // Arrange + var path = "/Views/Home/Index.cshtml"; + var fileProvider = new TestFileProvider(); + var fileInfo = fileProvider.AddFile(path, "some content"); + var viewCompiler = GetViewCompiler(fileProvider); + viewCompiler.AllowRecompilingViewsOnFileChange = true; // Act 1 var result1 = await viewCompiler.CompileAsync(path); @@ -125,6 +189,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); + viewCompiler.AllowRecompilingViewsOnFileChange = true; var expected2 = new CompiledViewDescriptor(); // Act 1 @@ -151,6 +216,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var fileProvider = new TestFileProvider(); var fileInfo = fileProvider.AddFile(path, "some content"); var viewCompiler = GetViewCompiler(fileProvider); + viewCompiler.AllowRecompilingViewsOnFileChange = true; var expected2 = new CompiledViewDescriptor(); // Act 1 @@ -327,6 +393,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + viewCompiler.AllowRecompilingViewsOnFileChange = true; // Act var result = await viewCompiler.CompileAsync(path); @@ -371,7 +438,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal } [Fact] - public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile() + public async Task CompileAsync_PrecompiledViewWithChecksum_DoesNotAddExpirationTokens() { // Arrange var path = "/Views/Home/Index.cshtml"; @@ -392,11 +459,43 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + // Act + var result = await viewCompiler.CompileAsync(path); + + // Assert + Assert.Same(precompiledView.Item, result.Item); + Assert.Empty(result.ExpirationTokens); + } + + [Fact] + public async Task CompileAsync_PrecompiledViewWithChecksum_CanRecompile() + { + // Arrange + var path = "/Views/Home/Index.cshtml"; + + var fileProvider = new TestFileProvider(); + var fileInfo = fileProvider.AddFile(path, "some content"); + + var expected2 = new CompiledViewDescriptor(); + + var precompiledView = new CompiledViewDescriptor + { + RelativePath = path, + Item = new TestRazorCompiledItem(typeof(string), "mvc.1.0.view", path, new object[] + { + new RazorSourceChecksumAttribute("SHA1", GetChecksum("some content"), path), + }), + }; + + var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + viewCompiler.AllowRecompilingViewsOnFileChange = true; + // Act - 1 var result = await viewCompiler.CompileAsync(path); // Assert - 1 Assert.Same(precompiledView.Item, result.Item); + Assert.NotEmpty(result.ExpirationTokens); // Act - 2 fileInfo.Content = "some other content"; @@ -427,6 +526,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + viewCompiler.AllowRecompilingViewsOnFileChange = true; // Act - 1 var result = await viewCompiler.CompileAsync(path); @@ -463,6 +563,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + viewCompiler.AllowRecompilingViewsOnFileChange = true; viewCompiler.Compile = _ => expected1; // Act - 1 @@ -504,6 +605,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal }; var viewCompiler = GetViewCompiler(fileProvider, precompiledViews: new[] { precompiledView }); + viewCompiler.AllowRecompilingViewsOnFileChange = true; // Act - 1 var result = await viewCompiler.CompileAsync(path); diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs index 1351b64fa5..fc28afe325 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs @@ -2,7 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -19,7 +23,8 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var hostingEnv = new Mock(); hostingEnv.SetupGet(e => e.ContentRootFileProvider) .Returns(expected); - var optionsSetup = new RazorViewEngineOptionsSetup(hostingEnv.Object); + + var optionsSetup = GetSetup(hostingEnvironment: hostingEnv.Object); // Act optionsSetup.Configure(options); @@ -28,5 +33,128 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal var fileProvider = Assert.Single(options.FileProviders); Assert.Same(expected, fileProvider); } + + [Fact] + public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_For21() + { + // Arrange + var options = new RazorViewEngineOptions(); + var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1); + + // Act + optionsSetup.Configure(options); + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.True(options.AllowRecompilingViewsOnFileChange); + } + + [Theory] + [InlineData(CompatibilityVersion.Version_2_2)] + [InlineData(CompatibilityVersion.Latest)] + public void PostConfigure_SetsAllowRecompilingViewsOnFileChange_InDevelopmentMode(CompatibilityVersion compatibilityVersion) + { + // Arrange + var options = new RazorViewEngineOptions(); + var hostingEnv = Mock.Of(env => env.EnvironmentName == EnvironmentName.Development); + var optionsSetup = GetSetup(compatibilityVersion, hostingEnv); + + // Act + optionsSetup.Configure(options); + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.True(options.AllowRecompilingViewsOnFileChange); + } + + [Theory] + [InlineData(CompatibilityVersion.Version_2_2)] + [InlineData(CompatibilityVersion.Latest)] + public void PostConfigure_DoesNotSetAllowRecompilingViewsOnFileChange_WhenNotInDevelopment(CompatibilityVersion compatibilityVersion) + { + // Arrange + var options = new RazorViewEngineOptions(); + var hostingEnv = Mock.Of(env => env.EnvironmentName == EnvironmentName.Staging); + var optionsSetup = GetSetup(compatibilityVersion, hostingEnv); + + // Act + optionsSetup.Configure(options); + optionsSetup.PostConfigure(string.Empty, options); + + // Assert + Assert.False(options.AllowRecompilingViewsOnFileChange); + } + + [Fact] + public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange_In21CompatMode() + { + // Arrange + var hostingEnv = Mock.Of(env => env.EnvironmentName == EnvironmentName.Staging); + var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_1 }; + var optionsSetup = GetSetup(CompatibilityVersion.Version_2_1, hostingEnv); + var serviceProvider = new ServiceCollection() + .AddOptions() + .AddSingleton>(optionsSetup) + .Configure(o => o.AllowRecompilingViewsOnFileChange = false) + .BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>(); + + // Assert + Assert.False(options.Value.AllowRecompilingViewsOnFileChange); + } + + [Fact] + public void RazorViewEngineOptionsSetup_ConfiguresAllowRecompilingViewsOnFileChange() + { + // Arrange + var hostingEnv = Mock.Of(env => env.EnvironmentName == EnvironmentName.Production); + var compatibilityVersion = new MvcCompatibilityOptions { CompatibilityVersion = CompatibilityVersion.Version_2_2 }; + var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv); + var serviceProvider = new ServiceCollection() + .AddOptions() + .AddSingleton>(optionsSetup) + .BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>(); + + // Assert + Assert.False(options.Value.AllowRecompilingViewsOnFileChange); + } + + [Fact] + public void RazorViewEngineOptionsSetup_DoesNotOverwriteAllowRecompilingViewsOnFileChange() + { + // Arrange + var hostingEnv = Mock.Of(env => env.EnvironmentName == EnvironmentName.Production); + var optionsSetup = GetSetup(CompatibilityVersion.Version_2_2, hostingEnv); + var serviceProvider = new ServiceCollection() + .AddOptions() + .AddSingleton>(optionsSetup) + .Configure(o => o.AllowRecompilingViewsOnFileChange = true) + .BuildServiceProvider(); + + // Act + var options = serviceProvider.GetRequiredService>(); + + // Assert + Assert.True(options.Value.AllowRecompilingViewsOnFileChange); + } + + private static RazorViewEngineOptionsSetup GetSetup( + CompatibilityVersion compatibilityVersion = CompatibilityVersion.Latest, + IHostingEnvironment hostingEnvironment = null) + { + hostingEnvironment = hostingEnvironment ?? Mock.Of(); + var compatibilityOptions = new MvcCompatibilityOptions { CompatibilityVersion = compatibilityVersion }; + + return new RazorViewEngineOptionsSetup( + hostingEnvironment, + NullLoggerFactory.Instance, + Options.Create(compatibilityOptions)); + } + } } diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs index ba35ac33d2..f522b54a49 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs @@ -8,6 +8,7 @@ using System.Threading; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Routing; @@ -1979,7 +1980,10 @@ namespace Microsoft.AspNetCore.Mvc.Razor IEnumerable areaViewLocationFormats = null, IEnumerable pageViewLocationFormats = null) { - var optionsSetup = new RazorViewEngineOptionsSetup(Mock.Of()); + var optionsSetup = new RazorViewEngineOptionsSetup( + Mock.Of(), + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions())); var options = new RazorViewEngineOptions(); optionsSetup.Configure(options); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs index 21f9db7215..48c7cb3454 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs @@ -30,8 +30,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var templateEngine = new RazorTemplateEngine( RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine, fileSystem); - var options = Options.Create(new RazorPagesOptions()); - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var razorPageOptions = Options.Create(new RazorPagesOptions()); + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, razorPageOptions, razorViewEngineOptions); // Act var changeToken = changeProvider.GetChangeToken(); @@ -57,8 +58,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal fileSystem); var options = Options.Create(new RazorPagesOptions()); options.Value.RootDirectory = rootDirectory; + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); // Act var changeToken = changeProvider.GetChangeToken(); @@ -81,7 +83,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine, fileSystem); var options = Options.Create(new RazorPagesOptions { AllowAreas = true }); - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); // Act var changeToken = changeProvider.GetChangeToken(); @@ -104,8 +107,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal templateEngine.Options.ImportsFileName = "_ViewImports.cshtml"; var options = Options.Create(new RazorPagesOptions()); options.Value.RootDirectory = "/dir1/dir2"; + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); // Act & Assert var compositeChangeToken = Assert.IsType(changeProvider.GetChangeToken()); @@ -131,7 +135,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal options.Value.RootDirectory = "/dir1/dir2"; options.Value.AllowAreas = true; - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); // Act & Assert var compositeChangeToken = Assert.IsType(changeProvider.GetChangeToken()); @@ -155,8 +160,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal fileSystem); templateEngine.Options.ImportsFileName = "_ViewImports.cshtml"; var options = Options.Create(new RazorPagesOptions { AllowAreas = false }); + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions { AllowRecompilingViewsOnFileChange = true }); - var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options); + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); // Act & Assert var compositeChangeToken = Assert.IsType(changeProvider.GetChangeToken()); @@ -164,5 +170,27 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal changeToken => Assert.Same(fileProvider.GetChangeToken("/_ViewImports.cshtml"), changeToken), changeToken => Assert.Same(fileProvider.GetChangeToken("/Pages/**/*.cshtml"), changeToken)); } + + [Fact] + public void GetChangeToken_DoesNotWatch_WhenOptionIsReset() + { + // Arrange + var fileProvider = new Mock(MockBehavior.Strict); + var accessor = Mock.Of(a => a.FileProvider == fileProvider.Object); + + var fileSystem = new FileProviderRazorProjectFileSystem(accessor, _hostingEnvironment); + var templateEngine = new RazorTemplateEngine( + RazorProjectEngine.Create(RazorConfiguration.Default, fileSystem).Engine, + fileSystem); + templateEngine.Options.ImportsFileName = "_ViewImports.cshtml"; + var options = Options.Create(new RazorPagesOptions()); + var razorViewEngineOptions = Options.Create(new RazorViewEngineOptions()); + + var changeProvider = new PageActionDescriptorChangeProvider(templateEngine, accessor, options, razorViewEngineOptions); + + // Act & Assert + var compositeChangeToken = Assert.IsType(changeProvider.GetChangeToken()); + fileProvider.Verify(f => f.Watch(It.IsAny()), Times.Never()); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs index e1571e451f..b0690ea559 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs @@ -2,9 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor; -using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -186,7 +187,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private static RazorViewEngineOptions GetViewEngineOptions() { - var defaultSetup = new RazorViewEngineOptionsSetup(Mock.Of()); + var defaultSetup = new RazorViewEngineOptionsSetup( + Mock.Of(), + NullLoggerFactory.Instance, + Options.Create(new MvcCompatibilityOptions())); var options = new RazorViewEngineOptions(); defaultSetup.Configure(options); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 2413eb34bf..2281f4d602 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -1,11 +1,14 @@ // 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.Hosting; using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.ObjectPool; using Microsoft.Extensions.Options; +using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.IntegrationTest @@ -32,6 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; + var razorViewEngineOptions = services.GetRequiredService>().Value; // Assert Assert.False(mvcOptions.AllowCombiningAuthorizeFilters); @@ -44,6 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Null(mvcOptions.MaxValidationDepth); Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.True(apiBehaviorOptions.SuppressMapClientErrors); + Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); } [Fact] @@ -61,6 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; + var razorViewEngineOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -73,6 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Null(mvcOptions.MaxValidationDepth); Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.True(apiBehaviorOptions.SuppressMapClientErrors); + Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); } [Fact] @@ -90,6 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; + var razorViewEngineOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -102,6 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(32, mvcOptions.MaxValidationDepth); Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.False(apiBehaviorOptions.SuppressMapClientErrors); + Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); } [Fact] @@ -119,6 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var jsonOptions = services.GetRequiredService>().Value; var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; + var razorViewEngineOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -131,11 +141,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.Equal(32, mvcOptions.MaxValidationDepth); Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.False(apiBehaviorOptions.SuppressMapClientErrors); + Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); } // This just does the minimum needed to be able to resolve these options. private static void AddHostingServices(IServiceCollection serviceCollection) { + serviceCollection.AddSingleton(Mock.Of()); serviceCollection.AddLogging(); serviceCollection.AddSingleton(); } diff --git a/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs b/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs new file mode 100644 index 0000000000..c93dba01e3 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs @@ -0,0 +1,18 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Primitives; + +namespace RazorPagesWebSite +{ + public class NonWatchingPhysicalFileProvider : PhysicalFileProvider, IFileProvider + { + public NonWatchingPhysicalFileProvider(string root) : base(root) + { + } + + IChangeToken IFileProvider.Watch(string filter) => throw new ArgumentException("This method should not be called."); + } +} diff --git a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs index 0ded866ccf..93d36cea31 100644 --- a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs +++ b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; using RazorPagesWebSite.Conventions; @@ -11,11 +12,18 @@ namespace RazorPagesWebSite { public class StartupWithBasePath { + private readonly IHostingEnvironment _hostingEnvironment; + + public StartupWithBasePath(IHostingEnvironment hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment; + } + public void ConfigureServices(IServiceCollection services) { services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => options.LoginPath = "/Login"); - services.AddMvc() + var builder = services.AddMvc() .AddCookieTempDataProvider() .AddRazorPagesOptions(options => { @@ -27,6 +35,14 @@ namespace RazorPagesWebSite options.Conventions.Add(new CustomModelTypeConvention()); }) .SetCompatibilityVersion(CompatibilityVersion.Latest); + + // Ensure we don't have code paths that call IFileProvider.Watch in the default code path. + // Comment this code block if you happen to run this site in Development. + builder.AddRazorOptions(options => + { + options.FileProviders.Clear(); + options.FileProviders.Add(new NonWatchingPhysicalFileProvider(_hostingEnvironment.ContentRootPath)); + }); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs b/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs index a504b11cb6..ed5a12a6f2 100644 --- a/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs +++ b/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs @@ -10,9 +10,16 @@ namespace RazorWebSite public IActionResult Index() => View("/Views/UpdateableIndex/Index.cshtml"); [HttpPost] - public IActionResult Update([FromServices] UpdateableFileProvider fileProvider) + public IActionResult Update([FromServices] UpdateableFileProvider fileProvider, string path, string content) { - fileProvider.UpdateContent("/Views/UpdateableShared/_Partial.cshtml", "New content"); + fileProvider.UpdateContent(path, content); + return Ok(); + } + + [HttpPost] + public IActionResult UpdateRazorPages([FromServices] UpdateableFileProvider fileProvider) + { + fileProvider.CancelRazorPages(); return Ok(); } } diff --git a/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs b/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs index 0e3ef8aaac..5613ffda9a 100644 --- a/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs +++ b/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using System.IO; using System.Text; @@ -13,8 +14,14 @@ namespace RazorWebSite { public class UpdateableFileProvider : IFileProvider { + public CancellationTokenSource _pagesTokenSource = new CancellationTokenSource(); + private readonly Dictionary _content = new Dictionary() { + { + "/Views/UpdateableIndex/_ViewImports.cshtml", + new TestFileInfo(string.Empty) + }, { "/Views/UpdateableIndex/Index.cshtml", new TestFileInfo(@"@Html.Partial(""../UpdateableShared/_Partial.cshtml"")") @@ -23,9 +30,21 @@ namespace RazorWebSite "/Views/UpdateableShared/_Partial.cshtml", new TestFileInfo("Original content") }, + { + "/Pages/UpdateablePage.cshtml", + new TestFileInfo("@page" + Environment.NewLine + "Original content") + }, }; - public IDirectoryContents GetDirectoryContents(string subpath) => new NotFoundDirectoryContents(); + public IDirectoryContents GetDirectoryContents(string subpath) + { + if (subpath == "/Pages") + { + return new PagesDirectoryContents(); + } + + return new NotFoundDirectoryContents(); + } public void UpdateContent(string subpath, string content) { @@ -34,10 +53,16 @@ namespace RazorWebSite _content[subpath] = new TestFileInfo(content); } + public void CancelRazorPages() + { + var oldToken = _pagesTokenSource; + _pagesTokenSource = new CancellationTokenSource(); + oldToken.Cancel(); + } + public IFileInfo GetFileInfo(string subpath) { - TestFileInfo fileInfo; - if (!_content.TryGetValue(subpath, out fileInfo)) + if (!_content.TryGetValue(subpath, out var fileInfo)) { fileInfo = new TestFileInfo(null); } @@ -47,8 +72,12 @@ namespace RazorWebSite public IChangeToken Watch(string filter) { - TestFileInfo fileInfo; - if (_content.TryGetValue(filter, out fileInfo)) + if (filter == "/Pages/**/*.cshtml") + { + return new CancellationChangeToken(_pagesTokenSource.Token); + } + + if (_content.TryGetValue(filter, out var fileInfo)) { return fileInfo.ChangeToken; } @@ -71,7 +100,7 @@ namespace RazorWebSite public bool IsDirectory => false; public DateTimeOffset LastModified => DateTimeOffset.MinValue; public long Length => -1; - public string Name => null; + public string Name { get; set; } public string PhysicalPath => null; public CancellationTokenSource TokenSource { get; } = new CancellationTokenSource(); public CancellationChangeToken ChangeToken { get; } @@ -81,5 +110,23 @@ namespace RazorWebSite return new MemoryStream(Encoding.UTF8.GetBytes(_content)); } } + + private class PagesDirectoryContents : IDirectoryContents + { + public bool Exists => true; + + public IEnumerator GetEnumerator() + { + var file = new TestFileInfo("@page" + Environment.NewLine + "Original content") + { + Name = "UpdateablePage.cshtml" + }; + + var files = new List { file }; + return files.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } } } From 863b1c2c97d49bed2357b240307a085308a9c593 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 7 Sep 2018 10:01:40 +1200 Subject: [PATCH 224/316] Action endpoint metadata contains attributes from action and controller (#8395) --- .../Abstractions/ActionDescriptor.cs | 3 ++ .../ControllerActionDescriptorBuilder.cs | 9 +++- ...ControllerActionDescriptorProviderTests.cs | 41 ++++++++++++++++++- .../PageActionDescriptorProviderTest.cs | 2 +- 4 files changed, 51 insertions(+), 4 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs index 20ab53e773..276f3179f3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs @@ -36,6 +36,9 @@ namespace Microsoft.AspNetCore.Mvc.Abstractions /// public IList ActionConstraints { get; set; } + /// + /// Gets or sets the endpoint metadata for this action. + /// public IList EndpointMetadata { get; set; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index 27a548c37a..4c7e4aa8f2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -138,10 +138,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal ActionModel action) { var defaultControllerConstraints = Enumerable.Empty(); + var defaultControllerEndpointMetadata = Enumerable.Empty(); if (controller.Selectors.Count > 0) { defaultControllerConstraints = controller.Selectors[0].ActionConstraints .Where(constraint => !(constraint is IRouteTemplateProvider)); + defaultControllerEndpointMetadata = controller.Selectors[0].EndpointMetadata; } var actionDescriptors = new List(); @@ -164,8 +166,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal AddActionConstraints(actionDescriptor, actionSelector, controllerConstraints); - // REVIEW: Need to get metadata from controller - actionDescriptor.EndpointMetadata = actionSelector.EndpointMetadata.ToList(); + // Metadata for the action is more significant so order it before the controller metadata + var actionDescriptorMetadata = actionSelector.EndpointMetadata.ToList(); + actionDescriptorMetadata.AddRange(defaultControllerEndpointMetadata); + + actionDescriptor.EndpointMetadata = actionDescriptorMetadata; } return actionDescriptors; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs index 9dbd548266..e35f55a9d9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.ApiExplorer; @@ -252,6 +253,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(nameof(ConventionallyRoutedController.ConventionalAction), actionConstraint.Value); } + [Fact] + public void GetDescriptors_EndpointMetadata_ContainsAttributesFromActionAndController() + { + // Arrange & Act + var descriptors = GetDescriptors( + typeof(AuthorizeController).GetTypeInfo()); + + // Assert + Assert.Equal(2, descriptors.Count()); + + var anonymousAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AllowAnonymousAction"); + + Assert.NotNull(anonymousAction.EndpointMetadata); + + Assert.Collection(anonymousAction.EndpointMetadata, + metadata => Assert.IsType(metadata), + metadata => Assert.IsType(metadata)); + + var authorizeAction = Assert.Single(descriptors, a => a.RouteValues["action"] == "AuthorizeAction"); + + Assert.NotNull(authorizeAction.EndpointMetadata); + + Assert.Collection(authorizeAction.EndpointMetadata, + metadata => Assert.Equal("ActionPolicy", Assert.IsType(metadata).Policy), + metadata => Assert.Equal("ControllerPolicy", Assert.IsType(metadata).Policy)); + } + [Fact] public void GetDescriptors_ActionWithHttpMethods_AddedToEndpointMetadata() { @@ -272,7 +300,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.False(httpMethodMetadata.AcceptCorsPreflight); Assert.Equal("GET", Assert.Single(httpMethodMetadata.HttpMethods)); - }); + }, + metadata => Assert.IsType(metadata)); } [Fact] @@ -1865,6 +1894,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal public void AttributeRoutedAction() { } } + [Authorize("ControllerPolicy")] + private class AuthorizeController + { + [AllowAnonymous] + public void AllowAnonymousAction() { } + + [Authorize("ActionPolicy")] + public void AuthorizeAction() { } + } + private class EmptyController { } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs index 4b1e2c4429..2573072e01 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs @@ -203,7 +203,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure } [Fact] - public void GetDescriptors_CopiesEndPointMetadataFromModel() + public void GetDescriptors_CopiesEndpointMetadataFromModel() { // Arrange var expected = new object(); From 74ba9898f485d688c69b60413fa6d11e9da9b019 Mon Sep 17 00:00:00 2001 From: Casey O'Brien Date: Thu, 6 Sep 2018 21:02:21 -0400 Subject: [PATCH 225/316] [Fixes #8250] Improve the error message when TEntryPoint is not in an entry point assembly Validates that the type used as a generic argument in WebApplicaitonFactory is contained within the entry point to assembly and throws InvalidOperationException otherwise --- .../Properties/Resources.Designer.cs | 14 +++++++++++++ .../Resources.resx | 3 +++ .../WebApplicationFactory.cs | 5 +++++ .../TestingInfrastructureTests.cs | 13 +++++++++++- .../ClassLibraryStartup.cs | 21 +++++++++++++++++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs index cd0ce4b518..16f29e1d67 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs @@ -38,6 +38,20 @@ namespace Microsoft.AspNetCore.Mvc.Testing internal static string FormatMissingDepsFile(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("MissingDepsFile"), p0, p1); + /// + /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. + /// + internal static string InvalidAssemblyEntryPoint + { + get => GetString("InvalidAssemblyEntryPoint"); + } + + /// + /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. + /// + internal static string FormatInvalidAssemblyEntryPoint(string p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx index 8174cf1f2d..adf4045ebe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. + No method 'public static {0} CreateWebHostBuilder(string[] args)' found on '{1}'. Alternatively, {2} can be extended and 'protected virtual {0} {3}()' can be overridden to provide your own {0} instance. diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index 356bd56d97..85fcdadba2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -251,6 +251,11 @@ namespace Microsoft.AspNetCore.Mvc.Testing private void EnsureDepsFile() { + if (typeof(TEntryPoint).Assembly.EntryPoint == null) + { + throw new InvalidOperationException(Resources.FormatInvalidAssemblyEntryPoint(typeof(TEntryPoint).Name)); + } + var depsFileName = $"{typeof(TEntryPoint).Assembly.GetName().Name}.deps.json"; var depsFile = new FileInfo(Path.Combine(AppContext.BaseDirectory, depsFileName)); if (!depsFile.Exists) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs index afa57b5092..54089a8e59 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Formatting; @@ -11,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Mvc.Testing.Handlers; using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; +using RazorPagesClassLibrary; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -39,6 +41,15 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("Test", response); } + [Fact] + public void TestingInfrastructure_CreateClientThrowsInvalidOperationForNonEntryPoint() + { + var factory = new WebApplicationFactory(); + var ex = Assert.Throws(() => factory.CreateClient()); + Assert.Equal($"The provided Type '{typeof(RazorPagesClassLibrary.ClassLibraryStartup).Name}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library.", + ex.Message); + } + [Fact] public async Task TestingInfrastructure_RedirectHandlerWorksWithPreserveMethod() { diff --git a/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs b/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs new file mode 100644 index 0000000000..0009c644a4 --- /dev/null +++ b/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace RazorPagesClassLibrary +{ + /// + /// Empty Startup for testing a Startup file within a class library + /// + public class ClassLibraryStartup + { + public void ConfigureServices(IServiceCollection services) + { + + } + + public void Configure(IApplicationBuilder app) + { + + } + } +} From fd80509fefb13c5cc1f263d2559a970a047f9668 Mon Sep 17 00:00:00 2001 From: gqqnbig Date: Fri, 7 Sep 2018 08:54:53 -0700 Subject: [PATCH 226/316] LocalizedHtmlString.Value doesn't include arguments (#8376) * Clarify that Value is prior to formatting with any constructor arguments --- .../LocalizedHtmlString.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs b/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs index ed91460d69..fdc99aeebc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs +++ b/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs @@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Mvc.Localization public string Name { get; } /// - /// The string resource. + /// The original resource string, prior to formatting with any constructor arguments. /// public string Value { get; } @@ -98,4 +98,4 @@ namespace Microsoft.AspNetCore.Mvc.Localization formattableString.WriteTo(writer, encoder); } } -} \ No newline at end of file +} From 72f7e52e1edcb70f265b786f47d5f212d9a27270 Mon Sep 17 00:00:00 2001 From: Joni Date: Thu, 6 Sep 2018 16:04:09 +0000 Subject: [PATCH 227/316] Remove blank line --- .../Internal/PagedBufferedTextWriter.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs index 3c436b3277..4c8b1937c9 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs @@ -39,7 +39,6 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal var length = _charBuffer.Length; if (length == 0) { - // If nothing sync buffered return CompletedTask, // so we can fast-path skip async state-machine creation return Task.CompletedTask; From 989559392680eac2b92a2acfe198659b10a78f32 Mon Sep 17 00:00:00 2001 From: Ryan Brandenburg Date: Thu, 6 Sep 2018 11:05:16 -0700 Subject: [PATCH 228/316] Include diagnosticMessages from xunit for Functional tests --- test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json | 1 + 1 file changed, 1 insertion(+) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json index 0d8a1f6a45..74f4888c0b 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json @@ -1,4 +1,5 @@ { + "diagnosticMessages": true, "shadowCopy": false, "longRunningTestSeconds": 60 } From 013697ad89d571665ba08e5d6bae3154ba55c531 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 6 Sep 2018 11:37:47 -0700 Subject: [PATCH 229/316] Remove experimental analyzers --- Mvc.NoFun.sln | 30 -- Mvc.sln | 30 -- .../ActionsMustNotBeAsyncVoidAnalyzer.cs | 56 ---- .../ActionsMustNotBeAsyncVoidFixProvider.cs | 58 ---- .../ApiActionsAreAttributeRoutedAnalyzer.cs | 52 --- ...ApiActionsAreAttributeRoutedFixProvider.cs | 187 ----------- ...ActionsShouldUseActionResultOfTAnalyzer.cs | 102 ------ ...ShouldUseActionResultOfTCodeFixProvider.cs | 55 ---- .../ApiControllerAnalyzerBase.cs | 39 --- .../ApiControllerAnalyzerContext.cs | 64 ---- .../CodeAnalysisExtensions.cs | 78 ----- .../ControllerAnalyzerBase.cs | 39 --- .../ControllerAnalyzerContext.cs | 47 --- .../DiagnosticDescriptors.cs | 46 --- ...pNetCore.Mvc.Analyzers.Experimental.csproj | 29 -- .../TypeNames.cs | 38 --- .../ActionsMustNotBeAsyncVoidFacts.cs | 173 ---------- .../ApiActionsAreAttributeRoutedFacts.cs | 304 ------------------ ...ApiActionsShouldUseActionResultOfTFacts.cs | 261 --------------- .../Infrastructure/AnalyzerTestBase.cs | 182 ----------- ...ore.Mvc.Analyzers.Experimental.Test.csproj | 20 -- .../xunit.runner.json | 3 - version.props | 8 - 23 files changed, 1901 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj delete mode 100644 src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj delete mode 100644 test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index d8e44d40bf..3995a5463a 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -105,12 +105,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{44546170-35BF-448F-88F5-4331AE67AEAE}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj", "{2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers", "src\Microsoft.AspNetCore.Mvc.Analyzers\Microsoft.AspNetCore.Mvc.Analyzers.csproj", "{30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{829D9A67-2D07-4CE6-86C0-59F2549B0CFA}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{0772E545-A674-4165-9469-E3D79D88A4A8}" @@ -507,18 +503,6 @@ Global {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|Mixed Platforms.Build.0 = Release|Any CPU {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|x86.ActiveCfg = Release|Any CPU {28D4DA20-6E13-47F9-80AE-D6AA7699CC35}.Release|x86.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Debug|x86.Build.0 = Debug|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Any CPU.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|x86.ActiveCfg = Release|Any CPU - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD}.Release|x86.Build.0 = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Any CPU.Build.0 = Debug|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -531,18 +515,6 @@ Global {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|Mixed Platforms.Build.0 = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|x86.ActiveCfg = Release|Any CPU {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3}.Release|x86.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|x86.ActiveCfg = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Debug|x86.Build.0 = Debug|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Any CPU.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|x86.ActiveCfg = Release|Any CPU - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754}.Release|x86.Build.0 = Release|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Any CPU.Build.0 = Debug|Any CPU {829D9A67-2D07-4CE6-86C0-59F2549B0CFA}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -641,9 +613,7 @@ Global {CF322BE1-E1FE-4CFD-8FCA-16A14B905D53} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {0AB46520-F441-4E01-B444-08F4D23F8B1B} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {28D4DA20-6E13-47F9-80AE-D6AA7699CC35} = {44546170-35BF-448F-88F5-4331AE67AEAE} - {2E6CDE10-8F96-4B75-B0D9-808F6A01B8BD} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {30862895-C1FA-49F5-B69A-B0F9F2ECD0F3} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {F8FD2D6A-DCD1-4A7B-B599-B728A12A1754} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {829D9A67-2D07-4CE6-86C0-59F2549B0CFA} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {0772E545-A674-4165-9469-E3D79D88A4A8} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} diff --git a/Mvc.sln b/Mvc.sln index 2bcf1d1580..775683a5fa 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -162,10 +162,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.An EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Analyzers.Test", "test\Mvc.Analyzers.Test\Mvc.Analyzers.Test.csproj", "{E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental", "src\Microsoft.AspNetCore.Mvc.Analyzers.Experimental\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj", "{CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test", "test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test\Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj", "{E83D3745-9BCF-40E8-8D34-AFBA604C2439}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorPagesClassLibrary", "test\WebSites\RazorPagesClassLibrary\RazorPagesClassLibrary.csproj", "{17122147-ADFD-41C8-87D9-CCC582CCA8F9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Views.TestCommon", "test\Microsoft.AspNetCore.Mvc.Views.TestCommon\Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj", "{51E3E785-A9D1-4196-BAFE-A17FF4304B89}" @@ -858,30 +854,6 @@ Global {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|Mixed Platforms.Build.0 = Release|Any CPU {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|x86.ActiveCfg = Release|Any CPU {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831}.Release|x86.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|x86.ActiveCfg = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Debug|x86.Build.0 = Debug|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Any CPU.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|x86.ActiveCfg = Release|Any CPU - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61}.Release|x86.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|x86.ActiveCfg = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Debug|x86.Build.0 = Debug|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Any CPU.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|Mixed Platforms.Build.0 = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|x86.ActiveCfg = Release|Any CPU - {E83D3745-9BCF-40E8-8D34-AFBA604C2439}.Release|x86.Build.0 = Release|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Any CPU.Build.0 = Debug|Any CPU {17122147-ADFD-41C8-87D9-CCC582CCA8F9}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU @@ -1031,8 +1003,6 @@ Global {8916DDCA-EC2A-4193-B9F3-78CAA1A96D5A} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {87A3E227-C45E-4141-A59F-402908E651FD} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {E3E09D2F-1FCF-4396-9B09-5A62CA8CC831} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} - {CBF23034-2249-4FE5-BD48-5F3CEAC0DF61} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} - {E83D3745-9BCF-40E8-8D34-AFBA604C2439} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {17122147-ADFD-41C8-87D9-CCC582CCA8F9} = {16703B76-C9F7-4C75-AE6C-53D92E308E3C} {51E3E785-A9D1-4196-BAFE-A17FF4304B89} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {910F023A-88E3-4CB4-8793-AC4005C7B421} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs deleted file mode 100644 index e3a489a960..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidAnalyzer.cs +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ActionsMustNotBeAsyncVoidAnalyzer : ControllerAnalyzerBase - { - public static readonly string ReturnTypeKey = "ReturnType"; - - public ActionsMustNotBeAsyncVoidAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid) - { - } - - protected override void InitializeWorker(ControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSyntaxNodeAction(context => - { - var methodSyntax = (MethodDeclarationSyntax)context.Node; - var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - - if (!analyzerContext.IsControllerAction(method)) - { - return; - } - - if (!method.IsAsync || !method.ReturnsVoid) - { - return; - } - - var returnType = analyzerContext.SystemThreadingTask.ToMinimalDisplayString( - context.SemanticModel, - methodSyntax.ReturnType.SpanStart); - - var properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(ReturnTypeKey, returnType); - - var location = methodSyntax.ReturnType.GetLocation(); - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - location, - properties: properties)); - - }, SyntaxKind.MethodDeclaration); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs deleted file mode 100644 index 8adf0c0288..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ActionsMustNotBeAsyncVoidFixProvider.cs +++ /dev/null @@ -1,58 +0,0 @@ -// 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.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ActionsMustNotBeAsyncVoidFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - if (context.Diagnostics.Length == 0) - { - return; - } - - if (!context.Diagnostics[0].Properties.TryGetValue(ActionsMustNotBeAsyncVoidAnalyzer.ReturnTypeKey, out var returnTypeName)) - { - return; - } - - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - const string title = "Fix async void usage."; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: CreateChangedDocumentAsync, - equivalenceKey: title), - context.Diagnostics); - - async Task CreateChangedDocumentAsync(CancellationToken cancellationToken) - { - var returnTypeSyntax = rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode(returnTypeSyntax, SyntaxFactory.IdentifierName(returnTypeName)); - - return editor.GetChangedDocument(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs deleted file mode 100644 index 32b7a4e3ac..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedAnalyzer.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ApiActionsAreAttributeRoutedAnalyzer : ApiControllerAnalyzerBase - { - internal const string MethodNameKey = "MethodName"; - - public ApiActionsAreAttributeRoutedAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted) - { - } - - protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSymbolAction(context => - { - var method = (IMethodSymbol)context.Symbol; - - if (!analyzerContext.IsApiAction(method)) - { - return; - } - - foreach (var attribute in method.GetAttributes()) - { - if (attribute.AttributeClass.IsAssignableFrom(analyzerContext.RouteAttribute)) - { - return; - } - } - - var properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(MethodNameKey, method.Name); - - var location = method.Locations.Length > 0 ? method.Locations[0] : Location.None; - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - location, - properties: properties)); - - }, SymbolKind.Method); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs deleted file mode 100644 index f89827b8f0..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsAreAttributeRoutedFixProvider.cs +++ /dev/null @@ -1,187 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Composition; -using System.Diagnostics; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ApiActionsAreAttributeRoutedFixProvider : CodeFixProvider - { - private static readonly RouteAttributeInfo[] RouteAttributes = new[] - { - new RouteAttributeInfo("HttpGet", TypeNames.HttpGetAttribute, new[] { "Get", "Find" }), - new RouteAttributeInfo("HttpPost", TypeNames.HttpPostAttribute, new[] { "Post", "Create", "Update" }), - new RouteAttributeInfo("HttpDelete", TypeNames.HttpDeleteAttribute, new[] { "Delete", "Remove" }), - new RouteAttributeInfo("HttpPut", TypeNames.HttpPutAttribute, new[] { "Put", "Create", "Update" }), - }; - - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - if (context.Diagnostics.Length == 0) - { - return; - } - - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - Debug.Assert(context.Diagnostics.Length == 1); - var diagnostic = context.Diagnostics[0]; - var methodName = diagnostic.Properties[ApiActionsAreAttributeRoutedAnalyzer.MethodNameKey]; - - var matchedByKeyword = false; - foreach (var routeInfo in RouteAttributes) - { - foreach (var keyword in routeInfo.KeyWords) - { - // Determine if the method starts with a conventional key and only show relevant routes. - // For e.g. FindPetByCategory would result in HttpGet attribute. - if (methodName.StartsWith(keyword, StringComparison.Ordinal)) - { - matchedByKeyword = true; - - var title = $"Add {routeInfo.Name} attribute"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(routeInfo.Type, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - } - - if (!matchedByKeyword) - { - foreach (var routeInfo in RouteAttributes) - { - var title = $"Add {routeInfo.Name} attribute"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(routeInfo.Type, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - - async Task CreateChangedDocumentAsync(string attributeName, CancellationToken cancellationToken) - { - var methodNode = (MethodDeclarationSyntax)rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - var compilation = editor.SemanticModel.Compilation; - var attributeMetadata = compilation.GetTypeByMetadataName(attributeName); - var fromRouteAttribute = compilation.GetTypeByMetadataName(TypeNames.FromRouteAttribute); - - attributeName = attributeMetadata.ToMinimalDisplayString(editor.SemanticModel, methodNode.SpanStart); - - // Remove the Attribute suffix from type names e.g. "HttpGetAttribute" -> "HttpGet" - if (attributeName.EndsWith("Attribute", StringComparison.Ordinal)) - { - attributeName = attributeName.Substring(0, attributeName.Length - "Attribute".Length); - } - - var method = editor.SemanticModel.GetDeclaredSymbol(methodNode); - - var attribute = SyntaxFactory.Attribute( - SyntaxFactory.ParseName(attributeName)); - - var route = GetRoute(fromRouteAttribute, method); - if (!string.IsNullOrEmpty(route)) - { - attribute = attribute.AddArgumentListArguments( - SyntaxFactory.AttributeArgument( - SyntaxFactory.LiteralExpression( - SyntaxKind.StringLiteralExpression, - SyntaxFactory.Literal(route)))); - } - - editor.AddAttribute(methodNode, attribute); - return editor.GetChangedDocument(); - } - } - - private static string GetRoute(ITypeSymbol fromRouteAttribute, IMethodSymbol method) - { - StringBuilder routeNameBuilder = null; - - foreach (var parameter in method.Parameters) - { - if (IsIdParameter(parameter.Name) || parameter.HasAttribute(fromRouteAttribute)) - { - if (routeNameBuilder == null) - { - routeNameBuilder = new StringBuilder(parameter.Name.Length + 2); - } - else - { - routeNameBuilder.Append("/"); - } - - routeNameBuilder - .Append("{") - .Append(parameter.Name) - .Append("}"); - } - } - - return routeNameBuilder?.ToString(); - } - - private static bool IsIdParameter(string name) - { - // Check if the parameter is named "id" (e.g. int id) or ends in Id (e.g. personId) - if (name == null || name.Length < 2) - { - return false; - } - - if (string.Equals("id", name, StringComparison.Ordinal)) - { - return true; - } - - if (name.Length > 3 && name.EndsWith("Id", StringComparison.Ordinal) && char.IsLower(name[name.Length - 3])) - { - return true; - } - - return false; - } - - private readonly struct RouteAttributeInfo - { - public RouteAttributeInfo(string name, string type, string[] keywords) - { - Name = name; - Type = type; - KeyWords = keywords; - } - - public string Name { get; } - - public string Type { get; } - - public string[] KeyWords { get; } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs deleted file mode 100644 index 9125d40692..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTAnalyzer.cs +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Immutable; -using System.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [DiagnosticAnalyzer(LanguageNames.CSharp)] - public class ApiActionsShouldUseActionResultOfTAnalyzer : ApiControllerAnalyzerBase - { - public static readonly string ReturnTypeKey = "ReturnType"; - - public ApiActionsShouldUseActionResultOfTAnalyzer() - : base(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf) - { - } - - protected override void InitializeWorker(ApiControllerAnalyzerContext analyzerContext) - { - analyzerContext.Context.RegisterSyntaxNodeAction(context => - { - var methodSyntax = (MethodDeclarationSyntax)context.Node; - if (methodSyntax.Body == null) - { - // Ignore expression bodied methods. - } - - var method = context.SemanticModel.GetDeclaredSymbol(methodSyntax, context.CancellationToken); - if (!analyzerContext.IsApiAction(method)) - { - return; - } - - if (method.ReturnsVoid || method.ReturnType.Kind != SymbolKind.NamedType) - { - return; - } - - var declaredReturnType = method.ReturnType; - var namedReturnType = (INamedTypeSymbol)method.ReturnType; - var isTaskOActionResult = false; - if (namedReturnType.ConstructedFrom?.IsAssignableFrom(analyzerContext.SystemThreadingTaskOfT) ?? false) - { - // Unwrap Task. - isTaskOActionResult = true; - declaredReturnType = namedReturnType.TypeArguments[0]; - } - - if (!declaredReturnType.IsAssignableFrom(analyzerContext.IActionResult)) - { - // Method signature does not look like IActionResult MyAction or SomeAwaitable. - // Nothing to do here. - return; - } - - // Method returns an IActionResult. Determine if the method block returns an ObjectResult - foreach (var returnStatement in methodSyntax.DescendantNodes().OfType()) - { - var returnType = context.SemanticModel.GetTypeInfo(returnStatement.Expression, context.CancellationToken); - if (returnType.Type == null || returnType.Type.Kind == SymbolKind.ErrorType) - { - continue; - } - - ImmutableDictionary properties = null; - if (returnType.Type.IsAssignableFrom(analyzerContext.ObjectResult)) - { - // Check if the method signature looks like "return Ok(userModelInstance)". If so, we can infer the type of userModelInstance - if (returnStatement.Expression is InvocationExpressionSyntax invocation && - invocation.ArgumentList.Arguments.Count == 1) - { - var typeInfo = context.SemanticModel.GetTypeInfo(invocation.ArgumentList.Arguments[0].Expression); - var desiredReturnType = analyzerContext.ActionResultOfT.Construct(typeInfo.Type); - if (isTaskOActionResult) - { - desiredReturnType = analyzerContext.SystemThreadingTaskOfT.Construct(desiredReturnType); - } - - var desiredReturnTypeString = desiredReturnType.ToMinimalDisplayString( - context.SemanticModel, - methodSyntax.ReturnType.SpanStart); - - properties = ImmutableDictionary.Create(StringComparer.Ordinal) - .Add(ReturnTypeKey, desiredReturnTypeString); - } - - context.ReportDiagnostic(Diagnostic.Create( - SupportedDiagnostic, - methodSyntax.ReturnType.GetLocation(), - properties: properties)); - } - } - }, SyntaxKind.MethodDeclaration); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs deleted file mode 100644 index 7dc5b5ec14..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiActionsShouldUseActionResultOfTCodeFixProvider.cs +++ /dev/null @@ -1,55 +0,0 @@ -// 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.Immutable; -using System.Composition; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Editing; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - [ExportCodeFixProvider(LanguageNames.CSharp)] - [Shared] - public class ApiActionsShouldUseActionResultOfTCodeFixProvider : CodeFixProvider - { - public sealed override ImmutableArray FixableDiagnosticIds => - ImmutableArray.Create(ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf.Id); - - public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; - - public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) - { - var rootNode = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); - - foreach (var diagnostic in context.Diagnostics) - { - if (diagnostic.Properties.TryGetValue("ReturnType", out var returnTypeName)) - { - - var title = $"Make return type {returnTypeName}"; - context.RegisterCodeFix( - CodeAction.Create( - title, - createChangedDocument: cancellationToken => CreateChangedDocumentAsync(returnTypeName, cancellationToken), - equivalenceKey: title), - context.Diagnostics); - } - } - - async Task CreateChangedDocumentAsync(string returnTypeName, CancellationToken cancellationToken) - { - var returnType = rootNode.FindNode(context.Span); - - var editor = await DocumentEditor.CreateAsync(context.Document, cancellationToken).ConfigureAwait(false); - editor.ReplaceNode(returnType, SyntaxFactory.IdentifierName(returnTypeName)); - - return editor.GetChangedDocument(); - } - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs deleted file mode 100644 index b70d1409de..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public abstract class ApiControllerAnalyzerBase : DiagnosticAnalyzer - { - public ApiControllerAnalyzerBase(DiagnosticDescriptor diagnostic) - { - SupportedDiagnostics = ImmutableArray.Create(diagnostic); - } - - protected DiagnosticDescriptor SupportedDiagnostic => SupportedDiagnostics[0]; - - public override ImmutableArray SupportedDiagnostics { get; } - - public sealed override void Initialize(AnalysisContext context) - { - context.RegisterCompilationStartAction(compilationContext => - { - var analyzerContext = new ApiControllerAnalyzerContext(compilationContext); - - // Only do work if ApiControllerAttribute is defined. - if (analyzerContext.ApiControllerAttribute == null) - { - return; - } - - InitializeWorker(analyzerContext); - }); - } - - protected abstract void InitializeWorker(ApiControllerAnalyzerContext analyzerContext); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs deleted file mode 100644 index 1ab36e3dac..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ApiControllerAnalyzerContext.cs +++ /dev/null @@ -1,64 +0,0 @@ -// 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.Linq; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiControllerAnalyzerContext - { -#pragma warning disable RS1012 // Start action has no registered actions. - public ApiControllerAnalyzerContext(CompilationStartAnalysisContext context) -#pragma warning restore RS1012 // Start action has no registered actions. - { - Context = context; - ApiControllerAttribute = context.Compilation.GetTypeByMetadataName(TypeNames.ApiControllerAttribute); - } - - public CompilationStartAnalysisContext Context { get; } - - public INamedTypeSymbol ApiControllerAttribute { get; } - - private INamedTypeSymbol _routeAttribute; - public INamedTypeSymbol RouteAttribute => GetType(TypeNames.IRouteTemplateProvider, ref _routeAttribute); - - private INamedTypeSymbol _actionResultOfT; - public INamedTypeSymbol ActionResultOfT => GetType(TypeNames.ActionResultOfT, ref _actionResultOfT); - - private INamedTypeSymbol _systemThreadingTask; - public INamedTypeSymbol SystemThreadingTask => GetType(TypeNames.Task, ref _systemThreadingTask); - - private INamedTypeSymbol _systemThreadingTaskOfT; - public INamedTypeSymbol SystemThreadingTaskOfT => GetType(TypeNames.TaskOfT, ref _systemThreadingTaskOfT); - - private INamedTypeSymbol _objectResult; - public INamedTypeSymbol ObjectResult => GetType(TypeNames.ObjectResult, ref _objectResult); - - private INamedTypeSymbol _iActionResult; - public INamedTypeSymbol IActionResult => GetType(TypeNames.IActionResult, ref _iActionResult); - - public INamedTypeSymbol _modelState; - public INamedTypeSymbol ModelStateDictionary => GetType(TypeNames.ModelStateDictionary, ref _modelState); - - public INamedTypeSymbol _nonActionAttribute; - public INamedTypeSymbol NonActionAttribute => GetType(TypeNames.NonActionAttribute, ref _nonActionAttribute); - - - private INamedTypeSymbol GetType(string name, ref INamedTypeSymbol cache) => - cache = cache ?? Context.Compilation.GetTypeByMetadataName(name); - - public bool IsApiAction(IMethodSymbol method) - { - return - method.ContainingType.HasAttribute(ApiControllerAttribute, inherit: true) && - method.DeclaredAccessibility == Accessibility.Public && - method.MethodKind == MethodKind.Ordinary && - !method.IsGenericMethod && - !method.IsAbstract && - !method.IsStatic && - !method.HasAttribute(NonActionAttribute); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs deleted file mode 100644 index a8ae5b8a0f..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/CodeAnalysisExtensions.cs +++ /dev/null @@ -1,78 +0,0 @@ -// 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.Diagnostics; -using Microsoft.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - internal static class CodeAnalysisExtensions - { - public static bool HasAttribute(this ITypeSymbol typeSymbol, ITypeSymbol attribute, bool inherit) - { - while (typeSymbol != null) - { - if (typeSymbol.HasAttribute(attribute)) - { - return true; - } - - typeSymbol = typeSymbol.BaseType; - } - - return false; - } - - public static bool HasAttribute(this ISymbol symbol, ITypeSymbol attribute) - { - Debug.Assert(symbol != null); - Debug.Assert(attribute != null); - - foreach (var declaredAttribute in symbol.GetAttributes()) - { - if (declaredAttribute.AttributeClass == attribute) - { - return true; - } - } - - return false; - } - - public static bool IsAssignableFrom(this ITypeSymbol source, INamedTypeSymbol target) - { - Debug.Assert(source != null); - Debug.Assert(target != null); - - if (source == target) - { - return true; - } - - if (target.TypeKind == TypeKind.Interface) - { - foreach (var @interface in source.AllInterfaces) - { - if (@interface == target) - { - return true; - } - } - - return false; - } - - do - { - if (source == target) - { - return true; - } - - source = source.BaseType; - } while (source != null); - - return false; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs deleted file mode 100644 index 71c497fa29..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerBase.cs +++ /dev/null @@ -1,39 +0,0 @@ -// 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.Immutable; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public abstract class ControllerAnalyzerBase : DiagnosticAnalyzer - { - public ControllerAnalyzerBase(DiagnosticDescriptor diagnostic) - { - SupportedDiagnostics = ImmutableArray.Create(diagnostic); - } - - protected DiagnosticDescriptor SupportedDiagnostic => SupportedDiagnostics[0]; - - public override ImmutableArray SupportedDiagnostics { get; } - - public sealed override void Initialize(AnalysisContext context) - { - context.RegisterCompilationStartAction(compilationContext => - { - var analyzerContext = new ControllerAnalyzerContext(compilationContext); - - // Only do work if ControllerAttribute is defined. - if (analyzerContext.ControllerAttribute == null) - { - return; - } - - InitializeWorker(analyzerContext); - }); - } - - protected abstract void InitializeWorker(ControllerAnalyzerContext analyzerContext); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs deleted file mode 100644 index 8a9d403a8a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/ControllerAnalyzerContext.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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.CodeAnalysis; -using Microsoft.CodeAnalysis.Diagnostics; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ControllerAnalyzerContext - { -#pragma warning disable RS1012 // Start action has no registered actions. - public ControllerAnalyzerContext(CompilationStartAnalysisContext context) -#pragma warning restore RS1012 // Start action has no registered actions. - { - Context = context; - ControllerAttribute = Context.Compilation.GetTypeByMetadataName(TypeNames.ControllerAttribute); - } - - public CompilationStartAnalysisContext Context { get; } - - public INamedTypeSymbol ControllerAttribute { get; } - - private INamedTypeSymbol _systemThreadingTask; - public INamedTypeSymbol SystemThreadingTask => GetType(TypeNames.Task, ref _systemThreadingTask); - - private INamedTypeSymbol _systemThreadingTaskOfT; - public INamedTypeSymbol SystemThreadingTaskOfT => GetType(TypeNames.TaskOfT, ref _systemThreadingTaskOfT); - - public INamedTypeSymbol _nonActionAttribute; - public INamedTypeSymbol NonActionAttribute => GetType(TypeNames.NonActionAttribute, ref _nonActionAttribute); - - private INamedTypeSymbol GetType(string name, ref INamedTypeSymbol cache) => - cache = cache ?? Context.Compilation.GetTypeByMetadataName(name); - - public bool IsControllerAction(IMethodSymbol method) - { - return - method.ContainingType.HasAttribute(ControllerAttribute, inherit: true) && - method.DeclaredAccessibility == Accessibility.Public && - method.MethodKind == MethodKind.Ordinary && - !method.IsGenericMethod && - !method.IsAbstract && - !method.IsStatic && - !method.HasAttribute(NonActionAttribute); - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs deleted file mode 100644 index 14f56b5918..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/DiagnosticDescriptors.cs +++ /dev/null @@ -1,46 +0,0 @@ -// 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.CodeAnalysis; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public static class ExperimentalDiagnosticDescriptors - { - public static readonly DiagnosticDescriptor MVC7000_ApiActionsMustBeAttributeRouted = - new DiagnosticDescriptor( - "MVC7000", - "Actions on types annotated with ApiControllerAttribute must be attribute routed.", - "Actions on types annotated with ApiControllerAttribute must be attribute routed.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7001_ApiActionsHaveBadModelStateFilter = - new DiagnosticDescriptor( - "MVC7001", - "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - "Actions on types annotated with ApiControllerAttribute do not require explicit ModelState validity check.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7002_ApiActionsShouldReturnActionResultOf = - new DiagnosticDescriptor( - "MVC7002", - "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - "Actions on types annotated with ApiControllerAttribute should return ActionResult.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - - public static readonly DiagnosticDescriptor MVC7003_ActionsMustNotBeAsyncVoid = - new DiagnosticDescriptor( - "MVC7003", - "Controller actions must not have async void signature.", - "Controller actions must not have async void signature.", - "Usage", - DiagnosticSeverity.Warning, - isEnabledByDefault: true); - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj deleted file mode 100644 index 9c4237c2ab..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.csproj +++ /dev/null @@ -1,29 +0,0 @@ - - - CSharp Analyzers for ASP.NET Core MVC. - aspnetcore;aspnetcoremvc - - false - $(ExperimentalVersionPrefix) - $(ExperimentalVersionSuffix) - $(ExperimentalPackageVersion) - - netstandard1.3 - false - false - false - - - - - - - - - - - - - - - diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs deleted file mode 100644 index 810c63a7bd..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers.Experimental/TypeNames.cs +++ /dev/null @@ -1,38 +0,0 @@ -// 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.Analyzers -{ - internal static class TypeNames - { - public const string ControllerAttribute = "Microsoft.AspNetCore.Mvc.ControllerAttribute"; - - public const string ApiControllerAttribute = "Microsoft.AspNetCore.Mvc.ApiControllerAttribute"; - - public const string NonActionAttribute = "Microsoft.AspNetCore.Mvc.NonActionAttribute"; - - public const string IRouteTemplateProvider = "Microsoft.AspNetCore.Mvc.Routing.IRouteTemplateProvider"; - - public const string ActionResultOfT = "Microsoft.AspNetCore.Mvc.ActionResult`1"; - - public const string Task = "System.Threading.Tasks.Task"; - - public const string TaskOfT = "System.Threading.Tasks.Task`1"; - - public const string ObjectResult = "Microsoft.AspNetCore.Mvc.ObjectResult"; - - public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; - - public const string ModelStateDictionary = "Microsoft.AspNetCore.Mvc.ModelBinding.ModelStateDictionary"; - - public const string HttpGetAttribute = "Microsoft.AspNetCore.Mvc.HttpGetAttribute"; - - public const string HttpPostAttribute = "Microsoft.AspNetCore.Mvc.HttpPostAttribute"; - - public const string HttpPutAttribute = "Microsoft.AspNetCore.Mvc.HttpPutAttribute"; - - public const string HttpDeleteAttribute = "Microsoft.AspNetCore.Mvc.HttpDeleteAttribute"; - - public const string FromRouteAttribute = "Microsoft.AspNetCore.Mvc.FromRouteAttribute"; - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs deleted file mode 100644 index d3fafb9f5c..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ActionsMustNotBeAsyncVoidFacts.cs +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ActionsMustNotBeAsyncVoidFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7003_ActionsMustNotBeAsyncVoid; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ActionsMustNotBeAsyncVoidAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ActionsMustNotBeAsyncVoidFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenMethodIsNotAControllerAction() - { - // Arrange - var test = -@" -using System.Threading.Tasks; - -public class UserViewModel -{ - public async void Index() => await Task.Delay(10); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenMethodIsAControllerAction() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 7, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async void Index() - { - await Response.Body.FlushAsync(); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async Task Index() - { - await Response.Body.FlushAsync(); - } -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionMethodIsExpressionBodied() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 7, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async void Index() => await Response.Body.FlushAsync(); -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -public class HomeController : Controller -{ - public async Task Index() => await Response.Body.FlushAsync(); -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFix_ProducesFullyQualifiedNamespaces() - { - // Arrange - var location = new DiagnosticLocation("Test.cs", 6, 18); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public async void Index() => await Response.Body.FlushAsync(); -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public async System.Threading.Tasks.Task Index() => await Response.Body.FlushAsync(); -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(location, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs deleted file mode 100644 index d7968d7928..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsAreAttributeRoutedFacts.cs +++ /dev/null @@ -1,304 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiActionsAreAttributeRoutedFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7000_ApiActionsMustBeAttributeRouted; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ApiActionsAreAttributeRoutedAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ApiActionsAreAttributeRoutedFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeIsNotApiController() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController : Controller -{ - public IActionResult Index() => View(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenApiControllerActionHasAttribute() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - [HttpGet] - public int GetPetId() => 0; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForConstructors() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - public PetController(){ } -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonActions() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController : Controller -{ - private int GetPetIdPrivate() => 0; - protected int GetPetIdProtected() => 0; - public static IActionResult FindPetByStatus(int status) => null; - [NonAction] - public object Reset(int state) => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAndCodeFixes_WhenApiControllerActionDoesNotHaveAttribute() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 8, 16); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -[Route] -public class PetController : Controller -{ - public int GetPetId() => 0; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -[Route] -public class PetController : Controller -{ - [HttpGet] - public int GetPetId() => 0; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_ApplyFullyQualifiedNames() - { - // Arrange - var test = -@" -[Microsoft.AspNetCore.Mvc.ApiController] -[Microsoft.AspNetCore.Mvc.Route] -public class PetController -{ - public object GetPet() => null; -}"; - var expectedFix = -@" -[Microsoft.AspNetCore.Mvc.ApiController] -[Microsoft.AspNetCore.Mvc.Route] -public class PetController -{ - [Microsoft.AspNetCore.Mvc.HttpGet] - public object GetPet() => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Theory] - [InlineData("id")] - [InlineData("petId")] - public async Task CodeFixes_WithIdParameter(string idParameter) - { - // Arrange - var test = -$@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{{ - public IActionResult Post(string notid, int {idParameter}) => null; -}}"; - var expectedFix = -$@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{{ - [HttpPost(""{{{idParameter}}}"")] - public IActionResult Post(string notid, int {idParameter}) => null; -}}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_WithRouteParameter() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - public IActionResult DeletePetByStatus([FromRoute] Status status, [FromRoute] Category category) => null; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - [HttpDelete(""{status}/{category}"")] - public IActionResult DeletePetByStatus([FromRoute] Status status, [FromRoute] Category category) => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task CodeFixes_WhenAttributeCannotBeInferred() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - public IActionResult ModifyPet() => null; -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -[ApiController] -[Route] -public class PetController -{ - [HttpPut] - public IActionResult ModifyPet() => null; -}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - // There isn't a good way to test all fixes simultaneously. We'll pick the last one to verify when we - // expect to have 4 fixes. - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics, codeFixIndex: 3); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs deleted file mode 100644 index 1cdd409ddc..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/ApiActionsShouldUseActionResultOfTFacts.cs +++ /dev/null @@ -1,261 +0,0 @@ -// 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.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.Diagnostics; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers -{ - public class ApiActionsShouldUseActionResultOfTFacts : AnalyzerTestBase - { - private static DiagnosticDescriptor DiagnosticDescriptor = ExperimentalDiagnosticDescriptors.MVC7002_ApiActionsShouldReturnActionResultOf; - - protected override DiagnosticAnalyzer DiagnosticAnalyzer { get; } - = new ApiActionsShouldUseActionResultOfTAnalyzer(); - - protected override CodeFixProvider CodeFixProvider { get; } - = new ApiActionsShouldUseActionResultOfTCodeFixProvider(); - - [Fact] - public async Task NoDiagnosticsAreReturned_FoEmptyScenarios() - { - // Arrange - var test = @""; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeIsNotApiController() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class HomeController: ControllerBase -{ - public IActionResult Index() => View(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_ForNonActions() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController: ControllerBaseBase -{ - private int GetPetIdPrivate() => 0; - protected int GetPetIdProtected() => 0; - public static IActionResult FindPetByStatus(int status) => null; - [NonAction] - public object Reset(int state) => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenActionAreExpressionBodiedMembers() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -[ApiController] -public class PetController: ControllerBase -{ - public IActionResult GetPetId() => ModelState.IsValid ? OK(new object()) : BadResult(); -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Theory] - [InlineData("Pet")] - [InlineData("List")] - [InlineData("System.Threading.Task")] - public async Task NoDiagnosticsAreReturned_WhenTypeReturnsNonObjectResult(string returnType) - { - // Arrange - var test = -$@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {{ }} - -[ApiController] -public class PetController: ControllerBase -{{ - public {returnType} GetPetId() => null; -}}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task NoDiagnosticsAreReturned_WhenTypeReturnsActionResultOfT() - { - // Arrange - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet { } - -[ApiController] -public class PetController: ControllerBase -{ - public ActionResult GetPetId() => null; -}"; - var project = CreateProject(test); - - // Act - var result = await GetDiagnosticAsync(project); - - // Assert - Assert.Empty(result); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionsReturnIActionResult() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 9, 12); - var test = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {} - -[ApiController] -public class PetController: ControllerBase -{ - public IActionResult GetPet() - { - return Ok(new Pet()); - } -}"; - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; - -public class Pet {} - -[ApiController] -public class PetController: ControllerBase -{ - public ActionResult GetPet() - { - return Ok(new Pet()); - } -}"; - var project = CreateProject(test); - - // Act - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - [Fact] - public async Task DiagnosticsAreReturned_WhenActionReturnsAsyncIActionResult() - { - // Arrange - var expectedLocation = new DiagnosticLocation("Test.cs", 8, 18); - - var test = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -[ApiController] -public class PetController: ControllerBase -{ - public async Task GetPet() - { - await Task.Delay(0); - return Ok(new Pet()); - } -} -public class Pet {}"; - - var expectedFix = -@" -using Microsoft.AspNetCore.Mvc; -using System.Threading.Tasks; - -[ApiController] -public class PetController: ControllerBase -{ - public async Task> GetPet() - { - await Task.Delay(0); - return Ok(new Pet()); - } -} -public class Pet {}"; - var project = CreateProject(test); - - // Act & Assert - var actualDiagnostics = await GetDiagnosticAsync(project); - AssertDiagnostic(expectedLocation, actualDiagnostics); - - var actualFix = await ApplyCodeFixAsync(project, actualDiagnostics); - Assert.Equal(expectedFix, actualFix, ignoreLineEndingDifferences: true); - } - - private void AssertDiagnostic(DiagnosticLocation expectedLocation, Diagnostic[] actualDiagnostics) - { - // Assert - Assert.Collection( - actualDiagnostics, - diagnostic => - { - Assert.Equal(DiagnosticDescriptor.Id, diagnostic.Id); - Assert.Same(DiagnosticDescriptor, diagnostic.Descriptor); - AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs deleted file mode 100644 index 1351b24c94..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Infrastructure/AnalyzerTestBase.cs +++ /dev/null @@ -1,182 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Analyzer.Testing; -using Microsoft.AspNetCore.Testing; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CodeActions; -using Microsoft.CodeAnalysis.CodeFixes; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.Diagnostics; -using Microsoft.CodeAnalysis.Text; -using Microsoft.Extensions.DependencyModel; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Analyzers.Infrastructure -{ - public abstract class AnalyzerTestBase : IDisposable - { - private static readonly object WorkspaceLock = new object(); - - public Workspace Workspace { get; private set; } - - protected abstract DiagnosticAnalyzer DiagnosticAnalyzer { get; } - - protected virtual CodeFixProvider CodeFixProvider { get; } - - public IDictionary MarkerLocations { get; } = new Dictionary(); - - public DiagnosticLocation DefaultMarkerLocation { get; private set; } - - protected Project CreateProjectFromFile([CallerMemberName] string fileName = "") - { - var solutionDirectory = TestPathUtilities.GetSolutionRootDirectory("Mvc"); - var projectDirectory = Path.Combine(solutionDirectory, "test", GetType().Assembly.GetName().Name); - - var filePath = Path.Combine(projectDirectory, "TestFiles", fileName + ".cs"); - if (!File.Exists(filePath)) - { - throw new FileNotFoundException($"TestFile {fileName} could not be found at {filePath}.", filePath); - } - - const string MarkerStart = "/*MM"; - const string MarkerEnd = "*/"; - - var lines = File.ReadAllLines(filePath); - for (var i = 0; i < lines.Length; i++) - { - var line = lines[i]; - var markerStartIndex = line.IndexOf(MarkerStart, StringComparison.Ordinal); - if (markerStartIndex != -1) - { - var markerEndIndex = line.IndexOf(MarkerEnd, markerStartIndex, StringComparison.Ordinal); - var markerName = line.Substring(markerStartIndex + 2, markerEndIndex - markerStartIndex - 2); - var resultLocation = new DiagnosticLocation(i + 1, markerStartIndex + 1); ; - - if (DefaultMarkerLocation == null) - { - DefaultMarkerLocation = resultLocation; - } - - MarkerLocations[markerName] = resultLocation; - line = line.Substring(0, markerStartIndex) + line.Substring(markerEndIndex + MarkerEnd.Length); - } - - lines[i] = line; - } - - var inputSource = string.Join(Environment.NewLine, lines); - return CreateProject(inputSource); - } - - protected Project CreateProject(string source) - { - var projectId = ProjectId.CreateNewId(debugName: "TestProject"); - var newFileName = "Test.cs"; - var documentId = DocumentId.CreateNewId(projectId, debugName: newFileName); - var metadataReferences = DependencyContext.Load(GetType().Assembly) - .CompileLibraries - .SelectMany(c => c.ResolveReferencePaths()) - .Select(path => MetadataReference.CreateFromFile(path)) - .Cast() - .ToList(); - - lock (WorkspaceLock) - { - if (Workspace == null) - { - Workspace = new AdhocWorkspace(); - } - } - - var solution = Workspace - .CurrentSolution - .AddProject(projectId, "TestProject", "TestProject", LanguageNames.CSharp) - .AddMetadataReferences(projectId, metadataReferences) - .AddDocument(documentId, newFileName, SourceText.From(source)); - - return solution.GetProject(projectId); - } - - protected async Task GetDiagnosticAsync(Project project) - { - var compilation = await project.GetCompilationAsync(); - var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(DiagnosticAnalyzer)); - var diagnostics = await compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync(); - return diagnostics.OrderBy(d => d.Location.SourceSpan.Start).ToArray(); - } - - protected Task ApplyCodeFixAsync( - Project project, - Diagnostic[] analyzerDiagnostic, - int codeFixIndex = 0) - { - var diagnostic = analyzerDiagnostic.Single(); - return ApplyCodeFixAsync(project, diagnostic, codeFixIndex); - } - - protected async Task ApplyCodeFixAsync( - Project project, - Diagnostic analyzerDiagnostic, - int codeFixIndex = 0) - { - if (CodeFixProvider == null) - { - throw new InvalidOperationException($"{nameof(CodeFixProvider)} has not been assigned."); - } - - var document = project.Documents.Single(); - var actions = new List(); - var context = new CodeFixContext(document, analyzerDiagnostic, (a, d) => actions.Add(a), CancellationToken.None); - await CodeFixProvider.RegisterCodeFixesAsync(context); - - if (actions.Count == 0) - { - throw new InvalidOperationException("CodeFix produced no actions to apply."); - } - - var updatedSolution = await ApplyFixAsync(actions[codeFixIndex]); - // Todo: figure out why this doesn't work. - // var updatedProject = updatedSolution.GetProject(project.Id); - // await EnsureCompilable(updatedProject); - - var updatedDocument = updatedSolution.GetDocument(document.Id); - var sourceText = await updatedDocument.GetTextAsync(); - return sourceText.ToString(); - } - - private static async Task EnsureCompilable(Project project) - { - var compilation = await project - .WithCompilationOptions(new CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary)) - .GetCompilationAsync(); - var diagnostics = compilation.GetDiagnostics(); - if (diagnostics.Length != 0) - { - var message = string.Join( - Environment.NewLine, - diagnostics.Select(d => CSharpDiagnosticFormatter.Instance.Format(d))); - throw new InvalidOperationException($"Compilation failed:{Environment.NewLine}{message}"); - } - } - - private static async Task ApplyFixAsync(CodeAction codeAction) - { - var operations = await codeAction.GetOperationsAsync(CancellationToken.None); - return Assert.Single(operations.OfType()).ChangedSolution; - } - - public void Dispose() - { - Workspace?.Dispose(); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj deleted file mode 100644 index 86f4228e01..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - $(StandardTestTfms) - true - - - - - - - - - - - - - - - diff --git a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json b/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json deleted file mode 100644 index 1c72a421ad..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Analyzers.Experimental.Test/xunit.runner.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "shadowCopy": false -} diff --git a/version.props b/version.props index e01d0a1143..7bf6e5246c 100644 --- a/version.props +++ b/version.props @@ -9,13 +9,5 @@ $(VersionPrefix)-$(VersionSuffix)-final $(FeatureBranchVersionPrefix)$(VersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) $(VersionSuffix)-$(BuildNumber) - - 0.2.0 - preview3 - - $(ExperimentalVersionPrefix) - $(ExperimentalVersionPrefix)-$(ExperimentalVersionSuffix)-final - $(FeatureBranchVersionPrefix)$(ExperimentalVersionSuffix)-$([System.Text.RegularExpressions.Regex]::Replace('$(FeatureBranchVersionSuffix)', '[^\w-]', '-')) - $(ExperimentalVersionSuffix)-$(BuildNumber) From 87c1389b5ae4709ce82b0d1a8dd2d0ca3bfeed65 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 9 Sep 2018 12:23:38 -0700 Subject: [PATCH 230/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 150 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 77 insertions(+), 77 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 4de4f3a0fd..fd1818d1af 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview2-35143 - 2.2.0-preview1-20180821.1 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-a-preview3-link-generator-16951 - 2.2.0-a-preview3-link-generator-16951 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview1-20180907.8 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 5.2.6 2.8.0 2.8.0 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 1.7.0 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 2.1.0 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 2.0.9 - 2.1.2 - 2.2.0-preview1-26618-02 - 2.2.0-preview2-35143 - 2.2.0-preview2-35143 + 2.1.3 + 2.2.0-preview2-26905-02 + 2.2.0-preview3-35202 + 2.2.0-preview3-35202 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index ad704918df..312f82f9a5 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180821.1 -commithash:c8d0cc52cd1abb697be24e288ffd54f8fae8bf17 +version:2.2.0-preview1-20180907.8 +commithash:078918eb5c1f176ee1da351c584fb4a4d7491aa0 From f573b8840a0473ff95cd69894ea1f0a6cf1daaa9 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 6 Sep 2018 17:05:11 -0700 Subject: [PATCH 231/316] Fix aspnet/Routing#782 Currently MVC is still running the IActionConstraint implementations for features that we've already moved into the routing layer. This has a significant perf cost associated with, and so we want to skip it because it's redundant. However if anyone has implemented their own `IActionConstraint`-based features, they still need to just work. This change takes the approach of skipping the action constraint phase at runtime unless we see something 'unknown'. This is an all or nothing choice, and will run action constraints if **any** action constraint we don't special case exists. This is the most compatible behavior (running redundant constraints) when the application is using constraints that the developer implemented. Another approach I considered was to eliminate these constraints as part of the process of building ADs. I don't think that's ideal because people have written code that introspects action constraints. We should consider something like this in 3.0. --- .../Internal/ActionConstraintCache.cs | 31 +++++++- .../Routing/ActionConstraintMatcherPolicy.cs | 6 +- .../CorsHttpMethodActionConstraint.cs | 1 + .../ActionConstraintMatcherPolicyTest.cs | 75 ++++++++++++++++++- 4 files changed, 108 insertions(+), 5 deletions(-) rename test/{Microsoft.AspNetCore.Mvc.Core.Test => Microsoft.AspNetCore.Mvc.Test}/Routing/ActionConstraintMatcherPolicyTest.cs (86%) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs index ff50a463b0..e8bf938aef 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -192,14 +192,43 @@ namespace Microsoft.AspNetCore.Mvc.Internal for (var i = 0; i < _actions.Items.Count; i++) { var action = _actions.Items[i]; - if (action.ActionConstraints?.Count > 0) + if (action.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action)) { + // We need to check for some specific action constraint implementations. + // We've implemented consumes, and HTTP method support inside endpoint routing, so + // we don't need to run an 'action constraint phase' if those are the only constraints. found = true; break; } } _hasActionConstraints = found; + + bool HasSignificantActionConstraint(ActionDescriptor action) + { + for (var i = 0; i < action.ActionConstraints.Count; i++) + { + var actionConstraint = action.ActionConstraints[i]; + if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint)) + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint") + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType() == typeof(ConsumesAttribute)) + { + // This one is OK, we implement this in endpoint routing. + } + else + { + return true; + } + } + + return false; + } } return _hasActionConstraints.Value; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index f858f4fe1e..e03640b59c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -35,6 +35,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Run really late. public override int Order => 100000; + // Internal for testing + internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints; + public Task ApplyAsync(HttpContext httpContext, EndpointFeature endpointFeature, CandidateSet candidateSet) { // PERF: we can skip over action constraints if there aren't any app-wide. @@ -42,8 +45,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Running action constraints (or just checking for them) in a candidate set // is somewhat expensive compared to other routing operations. This should only // happen if user-code adds action constraints. - var actions = _actionConstraintCache.CurrentCache; - if (actions.HasActionConstraints) + if (ShouldRunActionConstraints) { ApplyActionConstraints(httpContext, candidateSet); } diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs index 71ba2d696c..de3ff06468 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs +++ b/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs @@ -10,6 +10,7 @@ using Microsoft.AspNetCore.Mvc.Internal; namespace Microsoft.AspNetCore.Mvc.Cors.Internal { + // Don't casually change the name of this. We reference the full type name in ActionConstraintCache. public class CorsHttpMethodActionConstraint : HttpMethodActionConstraint { private readonly string OriginHeader = "Origin"; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs similarity index 86% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs rename to test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 5936d34ef6..9889b12e1b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -7,16 +7,17 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ActionConstraints; +using Microsoft.AspNetCore.Mvc.Cors.Internal; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.AspNetCore.Routing.Patterns; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Routing { + // These tests are intentionally in Mvc.Test so we can also test the CORS action constraint. public class ActionConstraintMatcherPolicyTest { [Fact] @@ -49,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing { ActionConstraints = new List() { - new HttpMethodActionConstraint(new string[] { "POST" }), + new BooleanConstraint() { Pass = true, }, }, Parameters = new List(), }; @@ -336,6 +337,76 @@ namespace Microsoft.AspNetCore.Mvc.Routing Assert.False(candidateSet[2].IsValidCandidate); } + [Fact] + public void ShouldRunActionConstraints_IgnoresIgnorableConstraints() + { + // Arrange + var actions = new ActionDescriptor[] + { + new ActionDescriptor() + { + + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new HttpMethodActionConstraint(new[]{ "GET", }), + }, + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new ConsumesAttribute("text/json"), + }, + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new CorsHttpMethodActionConstraint(new HttpMethodActionConstraint(new[]{ "GET", })), + }, + }, + }; + + var selector = CreateSelector(actions); + + // Act + var result = selector.ShouldRunActionConstraints; + + // Assert + Assert.False(result); + } + + [Fact] + public void ShouldRunActionConstraints_RunsForArbitraryActionConstraint() + { + // Arrange + var actions = new ActionDescriptor[] + { + new ActionDescriptor() + { + + }, + new ActionDescriptor() + { + ActionConstraints = new List() + { + new BooleanConstraint(), + }, + }, + }; + + var selector = CreateSelector(actions); + + // Act + var result = selector.ShouldRunActionConstraints; + + // Assert + Assert.True(result); + } + private ActionConstraintMatcherPolicy CreateSelector(ActionDescriptor[] actions) { // We need to actually provide some actions with some action constraints metadata From 6e27a04bf38f6634589fca10f7ca67859deaf8e6 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 11 Sep 2018 10:12:09 +1200 Subject: [PATCH 232/316] No HttpContext to route constraints in MvcEndpointDataSource (#8436) --- .../MvcEndpointDatasourceBenchmark.cs | 11 +---------- .../Internal/MvcEndpointDataSource.cs | 12 ++---------- .../Internal/MvcEndpointDataSourceTests.cs | 3 +-- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs index 893e8c8df8..62b520113b 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -110,8 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.Performance { var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, - new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - new MockServiceProvider()); + new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty()))); return dataSource; } @@ -126,14 +125,6 @@ namespace Microsoft.AspNetCore.Mvc.Performance public ActionDescriptorCollection ActionDescriptors { get; } } - private class MockServiceProvider : IServiceProvider - { - public object GetService(Type serviceType) - { - throw new NotImplementedException(); - } - } - private class MockParameterPolicyFactory : ParameterPolicyFactory { public override IParameterPolicy Create(RoutePatternParameterPart parameter, string inlineText) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 4c0304e380..9c1e7dd801 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -23,7 +23,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal { private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; - private readonly DefaultHttpContext _httpContextInstance; // The following are protected by this lock for WRITES only. This pattern is similar // to DefaultActionDescriptorChangeProvider - see comments there for details on @@ -35,8 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, - MvcEndpointInvokerFactory invokerFactory, - IServiceProvider serviceProvider) + MvcEndpointInvokerFactory invokerFactory) { if (actions == null) { @@ -48,14 +46,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(invokerFactory)); } - if (serviceProvider == null) - { - throw new ArgumentNullException(nameof(serviceProvider)); - } - _actions = actions; _invokerFactory = invokerFactory; - _httpContextInstance = new DefaultHttpContext() { RequestServices = serviceProvider }; ConventionalEndpointInfos = new List(); @@ -358,7 +350,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { foreach (var constraint in constraints) { - if (!constraint.Match(_httpContextInstance, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + if (!constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return false; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index e15b8225e0..17f88cea1a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -711,8 +711,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, - mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), - services.BuildServiceProvider()); + mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty()))); return dataSource; } From ec489da5862cec4c0a4f721436fabc47641146f7 Mon Sep 17 00:00:00 2001 From: "N. Taylor Mullen" Date: Mon, 10 Sep 2018 12:45:45 -0700 Subject: [PATCH 233/316] Add additional logging to diagnose flaky cache tag test. #8281 --- .../Pages/CacheTagHelper_VaryByCulture.cshtml | 5 +- .../TestCacheTagHelper.cs | 48 +++++++++++++++++++ 2 files changed, 51 insertions(+), 2 deletions(-) create mode 100644 test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml b/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml index bdf54e5728..97cb7f7d52 100644 --- a/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml @@ -1,4 +1,5 @@ @page +@addTagHelper *, HtmlGenerationWebSite @using System.Globalization @functions { @@ -9,6 +10,6 @@

@CultureInfo.CurrentCulture

@CultureInfo.CurrentUICulture

@CorrelationId - + @CorrelationId - + diff --git a/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs b/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs new file mode 100644 index 0000000000..e8f710d17e --- /dev/null +++ b/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs @@ -0,0 +1,48 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.TagHelpers.Cache; +using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Razor.TagHelpers; +using Microsoft.Extensions.Logging; + +namespace HtmlGenerationWebSite +{ + // This TagHelper enables us to investigate potential flakiness in the test that uses this tracked by https://github.com/aspnet/Mvc/issues/8281 + public class TestCacheTagHelper : CacheTagHelper + { + private readonly ILogger _logger; + + public TestCacheTagHelper( + CacheTagHelperMemoryCacheFactory factory, + HtmlEncoder htmlEncoder, + ILoggerFactory loggerFactory) : base(factory, htmlEncoder) + { + if (loggerFactory == null) + { + throw new ArgumentNullException(nameof(loggerFactory)); + } + + _logger = loggerFactory.CreateLogger(); + } + + public override Task ProcessAsync(TagHelperContext context, TagHelperOutput output) + { + var cacheKey = new CacheTagKey(this, context); + if (MemoryCache.TryGetValue(cacheKey, out var _)) + { + _logger.LogInformation("Cache entry exists with key: " + cacheKey.GenerateKey()); + } + else + { + _logger.LogInformation("Cache entry does NOT exist with key: " + cacheKey.GenerateKey()); + } + + return base.ProcessAsync(context, output); + } + } +} From 105f8b47a15299de690dcced925ea0134888fab1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 12 Sep 2018 21:16:50 +1200 Subject: [PATCH 234/316] Fix endpoint support for area/controller/action in attribute route (#8447) --- .../Builder/MvcEndpointInfo.cs | 27 ++- .../Internal/MvcEndpointDataSource.cs | 197 ++++++++++-------- .../Internal/MvcEndpointDataSourceTests.cs | 91 ++++++-- 3 files changed, 200 insertions(+), 115 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs index 8b1eba5fbf..161189b871 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Builder // Data we parse from the pattern will be used to fill in the rest of the constraints or // defaults. The parser will throw for invalid routes. ParsedPattern = RoutePatternFactory.Parse(pattern, defaults, constraints); - Constraints = BuildConstraints(parameterPolicyFactory); + ParameterPolicies = BuildParameterPolicies(ParsedPattern.Parameters, parameterPolicyFactory); Defaults = defaults; // Merge defaults outside of RoutePattern because the defaults will already have values from pattern @@ -50,33 +50,30 @@ namespace Microsoft.AspNetCore.Builder // Inline and non-inline defaults merged into one public RouteValueDictionary MergedDefaults { get; } - public IDictionary> Constraints { get; } + public IDictionary> ParameterPolicies { get; } public RouteValueDictionary DataTokens { get; } public RoutePattern ParsedPattern { get; private set; } - private Dictionary> BuildConstraints(ParameterPolicyFactory parameterPolicyFactory) + internal static Dictionary> BuildParameterPolicies(IReadOnlyList parameters, ParameterPolicyFactory parameterPolicyFactory) { - var constraints = new Dictionary>(StringComparer.OrdinalIgnoreCase); + var policies = new Dictionary>(StringComparer.OrdinalIgnoreCase); - foreach (var parameter in ParsedPattern.Parameters) + foreach (var parameter in parameters) { foreach (var parameterPolicy in parameter.ParameterPolicies) { var createdPolicy = parameterPolicyFactory.Create(parameter, parameterPolicy); - if (createdPolicy is IRouteConstraint routeConstraint) + if (!policies.TryGetValue(parameter.Name, out var policyList)) { - if (!constraints.TryGetValue(parameter.Name, out var paramConstraints)) - { - paramConstraints = new List(); - constraints.Add(parameter.Name, paramConstraints); - } - - paramConstraints.Add(routeConstraint); + policyList = new List(); + policies.Add(parameter.Name, policyList); } + + policyList.Add(createdPolicy); } } - return constraints; + return policies; } } -} +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 9c1e7dd801..82026635ca 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -9,7 +9,6 @@ using System.Text; using System.Threading; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; @@ -113,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // // Start with an order of '1' for conventional routes as attribute routes have a default order of '0'. // This is for scenarios dealing with migrating existing Router based code to Endpoint Routing world. - var conventionalRouteOrder = 0; + var conventionalRouteOrder = 1; // Check each of the conventional patterns to see if the action would be reachable // If the action and pattern are compatible then create an endpoint with the @@ -143,90 +142,38 @@ namespace Microsoft.AspNetCore.Mvc.Internal continue; } - var newPathSegments = endpointInfo.ParsedPattern.PathSegments.ToList(); - - for (var i = 0; i < newPathSegments.Count; i++) - { - // Check if the pattern can be shortened because the remaining parameters are optional - // - // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index - // can resolve to the following endpoints: - // - /Home/Index/{id?} - // - /Home - // - / - if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, endpointInfo, newPathSegments)) - { - var subPathSegments = newPathSegments.Take(i); - - var subEndpoint = CreateEndpoint( - action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, subPathSegments), - subPathSegments, - endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, - endpointInfo.DataTokens, - suppressLinkGeneration: false, - suppressPathMatching: false); - endpoints.Add(subEndpoint); - } - - List segmentParts = null; // Initialize only as needed - var segment = newPathSegments[i]; - for (var j = 0; j < segment.Parts.Count; j++) - { - var part = segment.Parts[j]; - - if (part.IsParameter && - part is RoutePatternParameterPart parameterPart && - action.RouteValues.ContainsKey(parameterPart.Name)) - { - if (segmentParts == null) - { - segmentParts = segment.Parts.ToList(); - } - - // Replace parameter with literal value - segmentParts[j] = RoutePatternFactory.LiteralPart(action.RouteValues[parameterPart.Name]); - } - } - - // A parameter part was replaced so replace segment with updated parts - if (segmentParts != null) - { - newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); - } - } - - var endpoint = CreateEndpoint( + conventionalRouteOrder = CreateEndpoints( + endpoints, + ref patternStringBuilder, action, - endpointInfo.Name, - GetPattern(ref patternStringBuilder, newPathSegments), - newPathSegments, + conventionalRouteOrder, + endpointInfo.ParsedPattern, + endpointInfo.MergedDefaults, endpointInfo.Defaults, - ++conventionalRouteOrder, - endpointInfo, + endpointInfo.Name, endpointInfo.DataTokens, + endpointInfo.ParameterPolicies, suppressLinkGeneration: false, suppressPathMatching: false); - endpoints.Add(endpoint); } } else { - var endpoint = CreateEndpoint( + var attributeRoutePattern = RoutePatternFactory.Parse(action.AttributeRouteInfo.Template); + + CreateEndpoints( + endpoints, + ref patternStringBuilder, action, - action.AttributeRouteInfo.Name, - action.AttributeRouteInfo.Template, - RoutePatternFactory.Parse(action.AttributeRouteInfo.Template).PathSegments, - nonInlineDefaults: null, action.AttributeRouteInfo.Order, - action.AttributeRouteInfo, + attributeRoutePattern, + attributeRoutePattern.Defaults, + nonInlineDefaults: null, + action.AttributeRouteInfo.Name, dataTokens: null, - suppressLinkGeneration: action.AttributeRouteInfo.SuppressLinkGeneration, - suppressPathMatching: action.AttributeRouteInfo.SuppressPathMatching); - endpoints.Add(endpoint); + allParameterPolicies: null, + action.AttributeRouteInfo.SuppressLinkGeneration, + action.AttributeRouteInfo.SuppressPathMatching); } } @@ -246,6 +193,93 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Step 4 - trigger old token oldCancellationTokenSource?.Cancel(); } + } + + // CreateEndpoints processes the route pattern, replacing area/controller/action parameters with endpoint values + // Because of default values it is possible for a route pattern to resolve to multiple endpoints + private int CreateEndpoints( + List endpoints, + ref StringBuilder patternStringBuilder, + ActionDescriptor action, + int routeOrder, + RoutePattern routePattern, + IReadOnlyDictionary allDefaults, + IReadOnlyDictionary nonInlineDefaults, + string name, + RouteValueDictionary dataTokens, + IDictionary> allParameterPolicies, + bool suppressLinkGeneration, + bool suppressPathMatching) + { + var newPathSegments = routePattern.PathSegments.ToList(); + + for (var i = 0; i < newPathSegments.Count; i++) + { + // Check if the pattern can be shortened because the remaining parameters are optional + // + // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index + // can resolve to the following endpoints: + // - /Home/Index/{id?} + // - /Home + // - / + if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments)) + { + var subPathSegments = newPathSegments.Take(i); + + var subEndpoint = CreateEndpoint( + action, + name, + GetPattern(ref patternStringBuilder, subPathSegments), + subPathSegments, + nonInlineDefaults, + routeOrder++, + dataTokens, + suppressLinkGeneration, + suppressPathMatching); + endpoints.Add(subEndpoint); + } + + List segmentParts = null; // Initialize only as needed + var segment = newPathSegments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + + if (part.IsParameter && + part is RoutePatternParameterPart parameterPart && + action.RouteValues.ContainsKey(parameterPart.Name)) + { + if (segmentParts == null) + { + segmentParts = segment.Parts.ToList(); + } + + var parameterRouteValue = action.RouteValues[parameterPart.Name]; + + segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); + } + } + + // A parameter part was replaced so replace segment with updated parts + if (segmentParts != null) + { + newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); + } + } + + var endpoint = CreateEndpoint( + action, + name, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, + nonInlineDefaults, + routeOrder++, + dataTokens, + suppressLinkGeneration, + suppressPathMatching); + endpoints.Add(endpoint); + + return routeOrder; string GetPattern(ref StringBuilder sb, IEnumerable segments) { @@ -265,7 +299,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private bool UseDefaultValuePlusRemainingSegmentsOptional( int segmentIndex, ActionDescriptor action, - MvcEndpointInfo endpointInfo, + IReadOnlyDictionary allDefaults, List pathSegments) { // Check whether the remaining segments are all optional and one or more of them is @@ -287,7 +321,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (action.RouteValues.ContainsKey(parameterPart.Name)) { - if (endpointInfo.MergedDefaults[parameterPart.Name] is string defaultValue + if (allDefaults.TryGetValue(parameterPart.Name, out var v) + && v is string defaultValue && action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) { @@ -346,11 +381,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal { // Check that the value matches against constraints on that parameter // e.g. For {controller:regex((Home|Login))} the controller value must match the regex - if (endpointInfo.Constraints.TryGetValue(routeKey, out var constraints)) + if (endpointInfo.ParameterPolicies.TryGetValue(routeKey, out var parameterPolicies)) { - foreach (var constraint in constraints) + foreach (var policy in parameterPolicies) { - if (!constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) + if (policy is IRouteConstraint constraint + && !constraint.Match(httpContext: null, NullRouter.Instance, routeKey, new RouteValueDictionary(action.RouteValues), RouteDirection.IncomingRequest)) { // Did not match constraint return false; @@ -372,7 +408,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal IEnumerable segments, object nonInlineDefaults, int order, - object source, RouteValueDictionary dataTokens, bool suppressLinkGeneration, bool suppressPathMatching) @@ -394,7 +429,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal action, routeName, new RouteValueDictionary(action.RouteValues), - source, dataTokens, suppressLinkGeneration, suppressPathMatching); @@ -413,7 +447,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal ActionDescriptor action, string routeName, RouteValueDictionary requiredValues, - object source, RouteValueDictionary dataTokens, bool suppressLinkGeneration, bool suppressPathMatching) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 17f88cea1a..03d280fdef 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -74,7 +74,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(displayName, matcherEndpoint.DisplayName); Assert.Equal(order, matcherEndpoint.Order); - Assert.Equal(template, matcherEndpoint.RoutePattern.RawText); + Assert.Equal("Template!", matcherEndpoint.RoutePattern.RawText); } [Fact] @@ -134,21 +134,41 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.True(actionInvokerCalled); } + public static TheoryData GetSingleActionData_Conventional + { + get => GetSingleActionData(true); + } + + public static TheoryData GetSingleActionData_Attribute + { + get => GetSingleActionData(false); + } + + private static TheoryData GetSingleActionData(bool isConventionalRouting) + { + var data = new TheoryData + { + {"{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, + {"{controller}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }}, + {"{action}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }}, + {"{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, + {"{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" }}, + {"{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }}, + {"{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }}, + {"{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }}, + {"{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" }}, + {"{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }}, + {"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }}, + {"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }}, + {"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }}, + }; + + return data; + } + [Theory] - [InlineData("{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" })] - [InlineData("{controller}/{id?}", new string[] { })] - [InlineData("{action}/{id?}", new string[] { })] - [InlineData("{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" })] - [InlineData("{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" })] - [InlineData("{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" })] - [InlineData("{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" })] - [InlineData("{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" })] - [InlineData("{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" })] - [InlineData("{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" })] - [InlineData("{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" })] - [InlineData("{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" })] - [InlineData("{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" })] - public void Endpoints_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) + [MemberData(nameof(GetSingleActionData_Conventional))] + public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -168,6 +188,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Collection(endpoints, inspectors); } + [Theory] + [MemberData(nameof(GetSingleActionData_Attribute))] + public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + attributeRouteTemplate: endpointInfoRoute, + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + var inspectors = finalEndpointPatterns + .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) + .ToArray(); + + // Assert + Assert.Collection(endpoints, inspectors); + } + [Theory] [InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] [InlineData("{controller}/{action}/{id?}", new string[] { })] @@ -740,11 +782,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal } private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params object[] requiredValues) + { + return GetActionDescriptorCollection(attributeRouteTemplate: null, requiredValues); + } + + private IActionDescriptorCollectionProvider GetActionDescriptorCollection(string attributeRouteTemplate, params object[] requiredValues) { var actionDescriptors = new List(); foreach (var requiredValue in requiredValues) { - actionDescriptors.Add(CreateActionDescriptor(requiredValue)); + actionDescriptors.Add(CreateActionDescriptor(requiredValue, attributeRouteTemplate)); } var actionDescriptorCollectionProviderMock = new Mock(); @@ -756,10 +803,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) { - return CreateActionDescriptor(new { controller = controller, action = action, area = area }); + return CreateActionDescriptor(new { controller = controller, action = action, area = area }, attributeRouteTemplate: null); } - private ActionDescriptor CreateActionDescriptor(object requiredValues) + private ActionDescriptor CreateActionDescriptor(object requiredValues, string attributeRouteTemplate = null) { var actionDescriptor = new ActionDescriptor(); var routeValues = new RouteValueDictionary(requiredValues); @@ -767,6 +814,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal { actionDescriptor.RouteValues[kvp.Key] = kvp.Value?.ToString(); } + if (!string.IsNullOrEmpty(attributeRouteTemplate)) + { + actionDescriptor.AttributeRouteInfo = new AttributeRouteInfo + { + Name = attributeRouteTemplate, + Template = attributeRouteTemplate + }; + } return actionDescriptor; } From dfae9c208a6752c8fed9c489d4c8848f02be35ec Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 12 Sep 2018 21:46:41 +1200 Subject: [PATCH 235/316] Add IParameterTransformer support (#8329) --- .../MvcEndpointDatasourceBenchmark.cs | 3 +- build/dependencies.props | 4 +- .../Routing/AttributeRouteInfo.cs | 2 + .../ApplicationModels/AttributeRouteModel.cs | 11 + .../ParameterTransformerConvention.cs | 41 +++ .../ControllerActionDescriptorBuilder.cs | 10 +- .../Internal/MvcEndpointDataSource.cs | 29 +- .../AttributeRouteModelTests.cs | 1 + .../RouteTokenTransformerConventionTest.cs | 101 +++++++ .../Internal/MvcEndpointDataSourceTests.cs | 38 ++- .../EndpointRoutingTest.cs | 278 ++++++++++++++++++ ...ntrollerRouteTokenTransformerConvention.cs | 38 +++ .../ConventionalTransformerController.cs | 27 ++ .../Controllers/EndpointRoutingController.cs | 36 +++ .../Controllers/OrderController.cs | 2 +- .../ParameterTransformerController.cs | 27 ++ .../Controllers/RouteDataController.cs | 2 +- ...emoveControllerActionDescriptorProvider.cs | 40 +++ test/WebSites/RoutingWebSite/Startup.cs | 21 +- .../RoutingWebSite/StartupWith21Compat.cs | 13 +- .../TestParameterTransformer.cs | 15 + 21 files changed, 723 insertions(+), 16 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs create mode 100644 test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs create mode 100644 test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs create mode 100644 test/WebSites/RoutingWebSite/TestParameterTransformer.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs index 62b520113b..028293dd28 100644 --- a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs @@ -110,7 +110,8 @@ namespace Microsoft.AspNetCore.Mvc.Performance { var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, - new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty()))); + new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), + new MockParameterPolicyFactory()); return dataSource; } diff --git a/build/dependencies.props b/build/dependencies.props index fd1818d1af..d5a1e4682b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview3-35202 2.2.0-preview3-35202 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-a-preview3-parameter-transformers-16968 + 2.2.0-a-preview3-parameter-transformers-16968 2.2.0-preview3-35202 2.2.0-preview3-35202 2.2.0-preview3-35202 diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs index 5531b44ced..f2fc4ea77b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs @@ -1,6 +1,8 @@ // 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.Routing; + namespace Microsoft.AspNetCore.Mvc.Routing { /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs index 07e6a20c48..3283a630a0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Text; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Mvc.ApplicationModels { @@ -220,6 +221,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels } public static string ReplaceTokens(string template, IDictionary values) + { + return ReplaceTokens(template, values, routeTokenTransformer: null); + } + + public static string ReplaceTokens(string template, IDictionary values, IParameterTransformer routeTokenTransformer) { var builder = new StringBuilder(); var state = TemplateParserState.Plaintext; @@ -371,6 +377,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels throw new InvalidOperationException(message); } + if (routeTokenTransformer != null) + { + value = routeTokenTransformer.Transform(value); + } + builder.Append(value); if (c == '[') diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs new file mode 100644 index 0000000000..d7645077af --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs @@ -0,0 +1,41 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that sets attribute routing token replacement + /// to use the specified on selectors. + /// + public class RouteTokenTransformerConvention : IActionModelConvention + { + private readonly IParameterTransformer _parameterTransformer; + + /// + /// Creates a new instance of with the specified . + /// + /// The to use with attribute routing token replacement. + public RouteTokenTransformerConvention(IParameterTransformer parameterTransformer) + { + if (parameterTransformer == null) + { + throw new ArgumentNullException(nameof(parameterTransformer)); + } + + _parameterTransformer = parameterTransformer; + } + + public void Apply(ActionModel action) + { + if (ShouldApply(action)) + { + action.Properties[typeof(IParameterTransformer)] = _parameterTransformer; + } + } + + protected virtual bool ShouldApply(ActionModel action) => true; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index 4c7e4aa8f2..174457bbcc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.Internal @@ -389,15 +390,20 @@ namespace Microsoft.AspNetCore.Mvc.Internal { try { + actionDescriptor.Properties.TryGetValue(typeof(IParameterTransformer), out var transformer); + var routeTokenTransformer = transformer as IParameterTransformer; + actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens( actionDescriptor.AttributeRouteInfo.Template, - actionDescriptor.RouteValues); + actionDescriptor.RouteValues, + routeTokenTransformer); if (actionDescriptor.AttributeRouteInfo.Name != null) { actionDescriptor.AttributeRouteInfo.Name = AttributeRouteModel.ReplaceTokens( actionDescriptor.AttributeRouteInfo.Name, - actionDescriptor.RouteValues); + actionDescriptor.RouteValues, + routeTokenTransformer); } } catch (InvalidOperationException ex) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 82026635ca..7f45a49b09 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { private readonly IActionDescriptorCollectionProvider _actions; private readonly MvcEndpointInvokerFactory _invokerFactory; + private readonly ParameterPolicyFactory _parameterPolicyFactory; // The following are protected by this lock for WRITES only. This pattern is similar // to DefaultActionDescriptorChangeProvider - see comments there for details on @@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal public MvcEndpointDataSource( IActionDescriptorCollectionProvider actions, - MvcEndpointInvokerFactory invokerFactory) + MvcEndpointInvokerFactory invokerFactory, + ParameterPolicyFactory parameterPolicyFactory) { if (actions == null) { @@ -45,8 +47,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new ArgumentNullException(nameof(invokerFactory)); } + if (parameterPolicyFactory == null) + { + throw new ArgumentNullException(nameof(parameterPolicyFactory)); + } + _actions = actions; _invokerFactory = invokerFactory; + _parameterPolicyFactory = parameterPolicyFactory; ConventionalEndpointInfos = new List(); @@ -253,9 +261,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal { segmentParts = segment.Parts.ToList(); } + if (allParameterPolicies == null) + { + allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); + } var parameterRouteValue = action.RouteValues[parameterPart.Name]; + // Replace parameter with literal value + if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) + { + // Check if the parameter has a transformer policy + // Use the first transformer policy + for (var k = 0; k < parameterPolicies.Count; k++) + { + if (parameterPolicies[k] is IParameterTransformer parameterTransformer) + { + parameterRouteValue = parameterTransformer.Transform(parameterRouteValue); + break; + } + } + } + segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs index c7a578850b..dc312a50bd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Reflection; +using Microsoft.AspNetCore.Routing; using Xunit; namespace Microsoft.AspNetCore.Mvc.ApplicationModels diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs new file mode 100644 index 0000000000..243ce755c6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs @@ -0,0 +1,101 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Routing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModel +{ + public class RouteTokenTransformerConventionTest + { + [Fact] + public void Apply_NullAttributeRouteModel_NoOp() + { + // Arrange + var convention = new RouteTokenTransformerConvention(new TestParameterTransformer()); + + var model = new ActionModel(GetMethodInfo(), Array.Empty()); + model.Selectors.Add(new SelectorModel() + { + AttributeRouteModel = null + }); + + // Act + convention.Apply(model); + + // Assert + Assert.Null(model.Selectors[0].AttributeRouteModel); + } + + [Fact] + public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer() + { + // Arrange + var transformer = new TestParameterTransformer(); + var convention = new RouteTokenTransformerConvention(transformer); + + var model = new ActionModel(GetMethodInfo(), Array.Empty()); + model.Selectors.Add(new SelectorModel() + { + AttributeRouteModel = new AttributeRouteModel() + }); + + // Act + convention.Apply(model); + + // Assert + Assert.True(model.Properties.TryGetValue(typeof(IParameterTransformer), out var routeTokenTransformer)); + Assert.Equal(transformer, routeTokenTransformer); + } + + [Fact] + public void Apply_ShouldApplyFalse_NoOp() + { + // Arrange + var transformer = new TestParameterTransformer(); + var convention = new CustomRouteTokenTransformerConvention(transformer); + + var model = new ActionModel(GetMethodInfo(), Array.Empty()); + model.Selectors.Add(new SelectorModel() + { + AttributeRouteModel = new AttributeRouteModel() + }); + + // Act + convention.Apply(model); + + // Assert + Assert.False(model.Properties.TryGetValue(typeof(IParameterTransformer), out _)); + } + + private MethodInfo GetMethodInfo() + { + return typeof(RouteTokenTransformerConventionTest).GetMethod(nameof(GetMethodInfo), BindingFlags.NonPublic | BindingFlags.Instance); + } + + private class TestParameterTransformer : IParameterTransformer + { + public string Transform(string value) + { + return value; + } + } + + private class CustomRouteTokenTransformerConvention : RouteTokenTransformerConvention + { + public CustomRouteTokenTransformerConvention(IParameterTransformer parameterTransformer) : base(parameterTransformer) + { + } + + protected override bool ShouldApply(ActionModel action) + { + return false; + } + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 03d280fdef..21fd57a11b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -161,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal {"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }}, {"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }}, {"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }}, + {"{controller:upper-case}/{action=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TestAction.{ext?}" }}, }; return data; @@ -217,6 +218,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] [InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", new[] { "TESTAREA/TestController/TestAction/{id?}" })] public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange @@ -230,6 +232,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); services.Configure(routeOptionsSetup.Configure); + services.Configure(options => + { + options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); + }); dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider())); @@ -377,6 +383,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [InlineData("{controller}/{action}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController1/TestAction3", "TestController2/TestAction1" })] [InlineData("{controller}/{action:regex((TestAction1|TestAction2))}", new[] { "TestController1/TestAction1", "TestController1/TestAction2", "TestController2/TestAction1" })] + [InlineData("{controller}/{action:regex((TestAction1|TestAction2)):upper-case}", new[] { "TestController1/TESTACTION1", "TestController1/TESTACTION2", "TestController2/TESTACTION1" })] public void Endpoints_MultipleActions(string endpointInfoRoute, string[] finalEndpointTemplates) { // Arrange @@ -750,14 +757,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal var services = new ServiceCollection(); services.AddSingleton(actionDescriptorCollectionProvider); + services.AddRouting(options => + { + options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); + }); + var serviceProvider = services.BuildServiceProvider(); var dataSource = new MvcEndpointDataSource( actionDescriptorCollectionProvider, - mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty()))); + mvcEndpointInvokerFactory ?? new MvcEndpointInvokerFactory(new ActionInvokerFactory(Array.Empty())), + serviceProvider.GetRequiredService()); return dataSource; } + private class UpperCaseParameterTransform : IParameterTransformer + { + public string Transform(string value) + { + return value?.ToUpperInvariant(); + } + } + private MvcEndpointInfo CreateEndpointInfo( string name, string template, @@ -768,13 +789,18 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (serviceProvider == null) { - var serviceCollection = new ServiceCollection(); - serviceCollection.AddRouting(); + var services = new ServiceCollection(); + services.AddRouting(); + services.AddSingleton(typeof(UpperCaseParameterTransform), new UpperCaseParameterTransform()); - var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); - serviceCollection.Configure(routeOptionsSetup.Configure); + var routeOptionsSetup = new MvcCoreRouteOptionsSetup(); + services.Configure(routeOptionsSetup.Configure); + services.Configure(options => + { + options.ConstraintMap["upper-case"] = typeof(UpperCaseParameterTransform); + }); - serviceProvider = serviceCollection.BuildServiceProvider(); + serviceProvider = services.BuildServiceProvider(); } var parameterPolicyFactory = serviceProvider.GetRequiredService(); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index f82be33703..73cab5f469 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -17,6 +17,170 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { } + [Fact] + public async Task ParameterTransformer_TokenReplacement_Found() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/_ParameterTransformer_/_Test_"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ParameterTransformer", result.Controller); + Assert.Equal("Test", result.Action); + } + + [Fact] + public async Task ParameterTransformer_TokenReplacement_NotFound() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ParameterTransformer/Test"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task AttributeRoutedAction_Parameters_Found() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/EndpointRouting/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("Index", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_Parameters_DefaultValue_Found() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/EndpointRouting"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("Index", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_Found() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/_EndpointRouting_/ParameterTransformer"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("ParameterTransformer", result.Action); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_NotFound() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/EndpointRouting/ParameterTransformer"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_LinkToSelf() + { + // Arrange + var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("ParameterTransformer", result.Action); + + Assert.Equal("/_EndpointRouting_/ParameterTransformer", result.Link); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_LinkWithAmbientController() + { + // Arrange + var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Get", id = 5 }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("ParameterTransformer", result.Action); + + Assert.Equal("/_EndpointRouting_/5", result.Link); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_LinkToAttributeRoutedController() + { + // Arrange + var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "ShowPosts", controller = "Blog" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("ParameterTransformer", result.Action); + + Assert.Equal("/Blog/ShowPosts", result.Link); + } + + [Fact] + public async Task AttributeRoutedAction_ParameterTransformer_LinkToConventionalController() + { + // Arrange + var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Index", controller = "Home" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("EndpointRouting", result.Controller); + Assert.Equal("ParameterTransformer", result.Action); + + Assert.Equal("/", result.Link); + } + [Fact] public async override Task HasEndpointMatch() { @@ -123,5 +287,119 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert Assert.Equal(HttpStatusCode.MethodNotAllowed, response.StatusCode); } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Index", result.Action); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_NotFound() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/ConventionalTransformer/Index"); + + // Assert + Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_DefaultValue() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Index", result.Action); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_WithParam() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Param", result.Action); + + Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", Assert.Single(result.ExpectedUrls)); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalController() + { + // Arrange + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Index", controller = "Home" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal("/", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalControllerWithParam() + { + // Arrange + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Param", controller = "ConventionalTransformer", param = "value" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_ParameterTransformer_LinkToSelf() + { + // Arrange + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new {}); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("ConventionalTransformer", result.Controller); + Assert.Equal("Index", result.Action); + Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_", result.Link); + } } } diff --git a/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs b/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs new file mode 100644 index 0000000000..37dca3f259 --- /dev/null +++ b/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs @@ -0,0 +1,38 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Routing; + +namespace RoutingWebSite +{ + public class ControllerRouteTokenTransformerConvention : IApplicationModelConvention + { + private readonly Type _controllerType; + private readonly IParameterTransformer _parameterTransformer; + + public ControllerRouteTokenTransformerConvention(Type controllerType, IParameterTransformer parameterTransformer) + { + if (parameterTransformer == null) + { + throw new ArgumentNullException(nameof(parameterTransformer)); + } + + _controllerType = controllerType; + _parameterTransformer = parameterTransformer; + } + + public void Apply(ApplicationModel application) + { + foreach (var controller in application.Controllers.Where(c => c.ControllerType == _controllerType)) + { + foreach (var action in controller.Actions) + { + action.Properties[typeof(IParameterTransformer)] = _parameterTransformer; + } + } + } + } +} diff --git a/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs b/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs new file mode 100644 index 0000000000..b31b2ee8b9 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs @@ -0,0 +1,27 @@ +// 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; + +namespace RoutingWebSite +{ + public class ConventionalTransformerController : Controller + { + private readonly TestResponseGenerator _generator; + + public ConventionalTransformerController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Index() + { + return _generator.Generate(); + } + + public IActionResult Param(string param) + { + return _generator.Generate($"/ConventionalTransformerRoute/_ConventionalTransformer_/Param/{param}"); + } + } +} diff --git a/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs new file mode 100644 index 0000000000..1163302277 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs @@ -0,0 +1,36 @@ +// 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; + +namespace RoutingWebSite +{ + [Route("/{controller:test-transformer}")] + public class EndpointRoutingController : Controller + { + private readonly TestResponseGenerator _generator; + + public EndpointRoutingController(TestResponseGenerator generator) + { + _generator = generator; + } + + [Route("/{controller}/{action=Index}")] + public IActionResult Index() + { + return _generator.Generate("/EndpointRouting/Index", "/EndpointRouting"); + } + + [Route("/{controller:test-transformer}/{action}")] + public IActionResult ParameterTransformer() + { + return _generator.Generate("/_EndpointRouting_/ParameterTransformer"); + } + + [Route("{id}")] + public IActionResult Get(int id) + { + return _generator.Generate("/_EndpointRouting_/" + id); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Controllers/OrderController.cs b/test/WebSites/RoutingWebSite/Controllers/OrderController.cs index 6f3f49ef2c..f0770ab539 100644 --- a/test/WebSites/RoutingWebSite/Controllers/OrderController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/OrderController.cs @@ -3,7 +3,7 @@ using Microsoft.AspNetCore.Mvc; -namespace RoutingWebSite.Controllers +namespace RoutingWebSite { [Route("Order/[action]/{orderId?}", Name = "Order_[action]")] public class OrderController : Controller diff --git a/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs b/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs new file mode 100644 index 0000000000..69991c60b5 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs @@ -0,0 +1,27 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace RoutingWebSite +{ + [Route("[controller]/[action]", Name = "[controller]_[action]")] + public class ParameterTransformerController : Controller + { + private readonly TestResponseGenerator _generator; + + public ParameterTransformerController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult Test() + { + return _generator.Generate("/_ParameterTransformer_/_Test_"); + } + } +} diff --git a/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs b/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs index e02f385707..97f49e00c4 100644 --- a/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; -namespace RoutingWebSite.Controllers +namespace RoutingWebSite { public class RouteDataController : Controller { diff --git a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs new file mode 100644 index 0000000000..40d40aa355 --- /dev/null +++ b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs @@ -0,0 +1,40 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; + +namespace RoutingWebSite +{ + public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider + { + private readonly Type _controllerType; + + public RemoveControllerActionDescriptorProvider(Type controllerType) + { + _controllerType = controllerType; + } + + public int Order => int.MaxValue; + + public void OnProvidersExecuted(ActionDescriptorProviderContext context) + { + } + + public void OnProvidersExecuting(ActionDescriptorProviderContext context) + { + foreach (var item in context.Results.ToList()) + { + if (item is ControllerActionDescriptor controllerActionDescriptor) + { + if (controllerActionDescriptor.ControllerTypeInfo == _controllerType) + { + context.Results.Remove(item); + } + } + } + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 08a67d963f..653aa4e81f 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -5,6 +5,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; namespace RoutingWebSite @@ -14,8 +15,20 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc() + services + .AddMvc(options => + { + // Add route token transformer to one controller + options.Conventions.Add(new ControllerRouteTokenTransformerConvention( + typeof(ParameterTransformerController), + new TestParameterTransformer())); + }) .SetCompatibilityVersion(CompatibilityVersion.Latest); + services + .AddRouting(options => + { + options.ConstraintMap["test-transformer"] = typeof(TestParameterTransformer); + }); services.AddScoped(); services.AddSingleton(); @@ -32,6 +45,12 @@ namespace RoutingWebSite constraints: new { controller = "DataTokens" }, dataTokens: new { hasDataTokens = true }); + routes.MapRoute( + "ConventionalTransformerRoute", + "ConventionalTransformerRoute/{controller:test-transformer}/{action=Index}/{param:test-transformer?}", + defaults: null, + constraints: new { controller = "ConventionalTransformer" }); + routes.MapAreaRoute( "flightRoute", "adminRoute", diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index eb891a53c5..44a6abfc61 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -1,11 +1,16 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; namespace RoutingWebSite { @@ -14,11 +19,17 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { - services.AddMvc() + services + .AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddScoped(); services.AddSingleton(); + + // EndpointRoutingController is not compatible with old routing + // Remove its action to avoid errors + var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(typeof(EndpointRoutingController)); + services.TryAddEnumerable(ServiceDescriptor.Singleton(actionDescriptorProvider)); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs new file mode 100644 index 0000000000..0af1f4ed7e --- /dev/null +++ b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs @@ -0,0 +1,15 @@ +// 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.Routing; + +namespace RoutingWebSite +{ + public class TestParameterTransformer : IParameterTransformer + { + public string Transform(string value) + { + return "_" + value + "_"; + } + } +} \ No newline at end of file From 43d4416a1d1bf0e568d66d884f9e591c26a8772b Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Thu, 13 Sep 2018 02:34:45 +1000 Subject: [PATCH 236/316] Fix typos (#8413) --- ...ireExplicitModelValidationCheckAnalyzer.cs | 8 +++--- .../Internal/FileProviderRazorProject.cs | 8 +++--- .../PageSaveTempDataPropertyFilter.cs | 2 +- .../ControllerSaveTempDataPropertyFilter.cs | 2 +- .../Internal/SaveTempDataFilter.cs | 2 +- .../SaveTempDataPropertyFilterBase.cs | 2 +- .../DefaultApiDescriptionProviderTest.cs | 6 ++-- .../Formatters/FormatterMappingsTest.cs | 6 ++-- .../DefaultApplicationModelProviderTest.cs | 4 +-- .../FloatingPointTypeModelBinderTestOfT.cs | 28 +++++++++---------- .../Binders/SimpleTypeModelBinderTest.cs | 6 ++-- .../DataAnnotationsModelValidatorTest.cs | 6 ++-- ...idationAttributeAdapterOfTAttributeTest.cs | 6 ++-- .../Internal/JsonResultExecutorTest.cs | 20 ++++++------- .../ApiExplorerTest.cs | 4 +-- .../ConsumesAttributeTestsBase.cs | 2 +- .../DefaultValuesTest.cs | 2 +- .../InputFormatterTests.cs | 4 +-- ...Site.HtmlGeneration_Home.Link.Encoded.html | 2 +- ...ationWebSite.HtmlGeneration_Home.Link.html | 2 +- .../ComplexTypeModelBinderIntegrationTest.cs | 2 +- .../SimpleTypeModelBinderIntegrationTest.cs | 4 +-- ...izationPageApplicationModelProviderTest.cs | 4 +-- .../PartialTagHelperTest.cs | 4 +-- .../ValidationSummaryTagHelperTest.cs | 2 +- .../Internal/DefaultDisplayTemplatesTest.cs | 4 +-- .../Internal/DefaultEditorTemplatesTest.cs | 4 +-- .../ViewDataAttributePropertyProviderTest.cs | 4 +-- .../CodeAnalysisExtensionsTest.cs | 6 ++-- ... GetAttributes_WithoutMethodOverriding.cs} | 2 +- ...isibilityDisabledByConventionController.cs | 4 +-- ...VisibilityEnabledByConventionController.cs | 4 +-- test/WebSites/ApiExplorerWebSite/Startup.cs | 2 +- ...> PolymorphicPropertyBindingController.cs} | 2 +- .../Views/HtmlGeneration_Home/Link.cshtml | 2 +- 35 files changed, 86 insertions(+), 86 deletions(-) rename test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/{GetAttributes_WithoutMethodOverridding.cs => GetAttributes_WithoutMethodOverriding.cs} (70%) rename test/WebSites/FormatterWebSite/Controllers/{PolymorhpicPropertyBindingController.cs => PolymorphicPropertyBindingController.cs} (90%) diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs index e8b52c012e..9928708f73 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -50,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers // if (!ModelState.IsValid) // or // if (ModelState.IsValid == false) - // If the conditional is misisng a true condition or has an else expression, skip this operation. + // If the conditional is missing a true condition or has an else expression, skip this operation. return; } @@ -170,8 +170,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers var constantValue = ((ILiteralOperation)operation).ConstantValue; if (!constantValue.HasValue || - !(constantValue.Value is bool boolCostantVaue) || - boolCostantVaue != expectedConstantValue) + !(constantValue.Value is bool boolConstantValue) || + boolConstantValue != expectedConstantValue) { return false; } @@ -199,7 +199,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers if (propertyReference.Instance?.Kind != OperationKind.PropertyReference) { - // Verify this is refering to the ModelState property on the current controller instance + // Verify this is referring to the ModelState property on the current controller instance return false; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs index 52af4ae3f3..9e5718fa2f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs @@ -16,20 +16,20 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly IFileProvider _provider; private readonly IHostingEnvironment _hostingEnvironment; - public FileProviderRazorProjectFileSystem(IRazorViewEngineFileProviderAccessor accessor, IHostingEnvironment hostingEnviroment) + public FileProviderRazorProjectFileSystem(IRazorViewEngineFileProviderAccessor accessor, IHostingEnvironment hostingEnvironment) { if (accessor == null) { throw new ArgumentNullException(nameof(accessor)); } - if (hostingEnviroment == null) + if (hostingEnvironment == null) { - throw new ArgumentNullException(nameof(hostingEnviroment)); + throw new ArgumentNullException(nameof(hostingEnvironment)); } _provider = accessor.FileProvider; - _hostingEnvironment = hostingEnviroment; + _hostingEnvironment = hostingEnvironment; } public override RazorProjectItem GetItem(string path) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs index 73633bf59c..e3f3355fde 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages Subject = context.HandlerInstance; var tempData = _tempDataFactory.GetTempData(context.HttpContext); - SetPropertyVaules(tempData); + SetPropertyValues(tempData); } public void OnPageHandlerExecuted(PageHandlerExecutedContext context) diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs index 96228d5b7b..e375ae94bc 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures Subject = context.Controller; var tempData = _tempDataFactory.GetTempData(context.HttpContext); - SetPropertyVaules(tempData); + SetPropertyValues(tempData); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs index 2ecf4cbbef..52e98e0677 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal /// public void OnResourceExecuted(ResourceExecutedContext context) { - // If there is an unhandled exception, we would like to avoid setting tempdata as + // If there is an unhandled exception, we would like to avoid setting tempdata as // the end user is going to see an error page anyway and also it helps us in avoiding // accessing resources like Session too late in the request lifecyle where SessionFeature might // not be available. diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs index c560584798..ab4c79dff8 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal /// Sets the values of the properties of from . /// /// The . - protected void SetPropertyVaules(ITempDataDictionary tempData) + protected void SetPropertyValues(ITempDataDictionary tempData) { if (Properties == null) { diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs index e4a446485d..81f17cb58b 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs @@ -1181,7 +1181,7 @@ namespace Microsoft.AspNetCore.Mvc.Description } [Fact] - public void GetApiDescription_ParameterDescription_IsRequiredNotSet_IfNotValiatingTopLevelNodes() + public void GetApiDescription_ParameterDescription_IsRequiredNotSet_IfNotValidatingTopLevelNodes() { // Arrange var action = CreateActionDescriptor(nameof(RequiredParameter)); @@ -2081,7 +2081,7 @@ namespace Microsoft.AspNetCore.Mvc.Description { } - private void AcceptsRedundantMetadata([FromQuery] RedundentMetadata r) + private void AcceptsRedundantMetadata([FromQuery] RedundantMetadata r) { } @@ -2247,7 +2247,7 @@ namespace Microsoft.AspNetCore.Mvc.Description public string Name { get; set; } } - private class RedundentMetadata + private class RedundantMetadata { [FromQuery] public int Id { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs index 15252dc783..a181b5528b 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs @@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters var options = new FormatterMappings(); options.SetMediaTypeMappingForFormat(setFormat, MediaTypeHeaderValue.Parse(contentType)); - // Act + // Act var returnMediaType = options.GetMediaTypeMappingForFormat(getFormat); // Assert @@ -78,7 +78,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters [InlineData("application/*")] [InlineData("*/json")] [InlineData("*/*")] - public void FormatterMappings_Wildcardformat(string format) + public void FormatterMappings_WildcardFormat(string format) { // Arrange var options = new FormatterMappings(); @@ -117,7 +117,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters mediaType = MediaTypeHeaderValue.Parse("application/bar"); options.SetMediaTypeMappingForFormat("bar", mediaType); - // Act + // Act var cleared = options.ClearMediaTypeMappingForFormat(format); // Assert diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs index 4af3ea19f5..98e358cabc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs @@ -869,7 +869,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Fact] - public void CreateActionModel_AttributeRouteOnAction_CreatesOneActionInforPerRouteTemplate() + public void CreateActionModel_AttributeRouteOnAction_CreatesOneActionInfoPerRouteTemplate() { // Arrange var builder = new TestApplicationModelProvider(); @@ -951,7 +951,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [InlineData(typeof(SingleRouteAttributeController))] [InlineData(typeof(MultipleRouteAttributeController))] - public void CreateActionModel_RouteOnController_CreatesOneActionInforPerRouteTemplateOnAction(Type controller) + public void CreateActionModel_RouteOnController_CreatesOneActionInfoPerRouteTemplateOnAction(Type controller) { // Arrange var builder = new TestApplicationModelProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs index fbcb7e5893..0a8f69b60f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs @@ -11,7 +11,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { public abstract class FloatingPointTypeModelBinderTest where TFloatingPoint: struct { - public static TheoryData ConvertableTypeData + public static TheoryData ConvertibleTypeData { get { @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders protected abstract TFloatingPoint ThirtyTwoThousandPointOne { get; } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsFailure_IfAttemptedValueCannotBeParsed(Type destinationType) { // Arrange @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_IfAttemptedValueCannotBeParsed(Type destinationType) { // Arrange @@ -76,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_IfAttemptedValueCannotBeCompletelyParsed(Type destinationType) { // Arrange @@ -100,7 +100,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_IfAttemptedValueContainsDisallowedWhitespace(Type destinationType) { // Arrange @@ -124,7 +124,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_IfAttemptedValueContainsDisallowedDecimal(Type destinationType) { // Arrange @@ -148,7 +148,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_IfAttemptedValueContainsDisallowedThousandsSeparator(Type destinationType) { // Arrange @@ -172,7 +172,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsFailed_IfValueProviderEmpty(Type destinationType) { // Arrange @@ -236,7 +236,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_Twelve(Type destinationType) { // Arrange @@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] [ReplaceCulture("en-GB", "en-GB")] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_TwelvePointFive(Type destinationType) { @@ -279,7 +279,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_FrenchTwelvePointFive(Type destinationType) { // Arrange @@ -300,7 +300,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_ThirtyTwoThousand(Type destinationType) { // Arrange @@ -321,7 +321,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_ThirtyTwoThousandPointOne(Type destinationType) { // Arrange @@ -342,7 +342,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsModel_IfAttemptedValueIsValid_FrenchThirtyTwoThousandPointOne(Type destinationType) { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs index 30623f3c72..e50098b146 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs @@ -66,7 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.True(bindingContext.ModelState.ContainsKey("theModelName")); } - public static TheoryData ConvertableTypeData + public static TheoryData ConvertibleTypeData { get { @@ -92,7 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_ReturnsFailure_IfTypeCanBeConverted_AndConversionFails(Type destinationType) { // Arrange @@ -112,7 +112,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [MemberData(nameof(ConvertableTypeData))] + [MemberData(nameof(ConvertibleTypeData))] public async Task BindModel_CreatesError_WhenTypeConversionIsNull(Type destinationType) { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs index 17669fd0d4..b67c769f57 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs @@ -262,7 +262,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal } public static TheoryData, IEnumerable> - Valdate_ReturnsExpectedResults_Data + Validate_ReturnsExpectedResults_Data { get { @@ -327,8 +327,8 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal } [Theory] - [MemberData(nameof(Valdate_ReturnsExpectedResults_Data))] - public void Valdate_ReturnsExpectedResults( + [MemberData(nameof(Validate_ReturnsExpectedResults_Data))] + public void Validate_ReturnsExpectedResults( string errorMessage, IEnumerable memberNames, IEnumerable expectedResults) diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs index e616795fcf..3febb5f4e5 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs @@ -39,16 +39,16 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal adapter.GetErrorMessage(validationContext); // Assert - Assert.True(attribute.Formated); + Assert.True(attribute.Formatted); } public class TestValidationAttribute : ValidationAttribute { - public bool Formated = false; + public bool Formatted = false; public override string FormatErrorMessage(string name) { - Formated = true; + Formatted = true; return base.FormatErrorMessage(name); } } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs index 81452cc818..2dbbeb1dad 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var context = GetActionContext(); var result = new JsonResult(new { foo = "abcd" }); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var result = new JsonResult(new { foo = "abcd" }); result.ContentType = "text/json"; - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -75,7 +75,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal { Encoding = Encoding.ASCII }.ToString(); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -97,7 +97,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal context.HttpContext.Response.ContentType = expectedContentType; var result = new JsonResult(new { foo = "abcd" }); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -122,7 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal context.HttpContext.Response.ContentType = responseContentType; var result = new JsonResult(new { foo = "abcd" }); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal serializerSettings.Formatting = Formatting.Indented; var result = new JsonResult(new { foo = "abcd" }, serializerSettings); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act await executor.ExecuteAsync(context, result); @@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var expected = Encoding.UTF8.GetBytes("{\"name\":\"Robert\""); var context = GetActionContext(); var result = new JsonResult(new ModelWithSerializationError()); - var executor = CreateExcutor(); + var executor = CreateExecutor(); // Act try @@ -190,7 +190,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var expected = "Executing JsonResult, writing value of type 'System.String'."; var context = GetActionContext(); var logger = new StubLogger(); - var executer = CreateExcutor(logger); + var executer = CreateExecutor(logger); var result = new JsonResult("result_value"); // Act @@ -207,7 +207,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var expected = "Executing JsonResult, writing value of type 'null'."; var context = GetActionContext(); var logger = new StubLogger(); - var executer = CreateExcutor(logger); + var executer = CreateExecutor(logger); var result = new JsonResult(null); // Act @@ -217,7 +217,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal Assert.Equal(expected, logger.MostRecentMessage); } - private static JsonResultExecutor CreateExcutor(ILogger logger = null) + private static JsonResultExecutor CreateExecutor(ILogger logger = null) { return new JsonResultExecutor( new TestHttpResponseStreamWriterFactory(), diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index 6b4b7acd73..a726552c73 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ApiExplorer_IsVisible_EnabledWithConvention() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ApiExplorerVisbilityEnabledByConvention"); + var response = await Client.GetAsync("http://localhost/ApiExplorerVisibilityEnabledByConvention"); var body = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject>(body); @@ -42,7 +42,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ApiExplorer_IsVisible_DisabledWithConvention() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ApiExplorerVisbilityDisabledByConvention"); + var response = await Client.GetAsync("http://localhost/ApiExplorerVisibilityDisabledByConvention"); var body = await response.Content.ReadAsStringAsync(); var result = JsonConvert.DeserializeObject>(body); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs index 71502e6f24..9a978d2662 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs @@ -107,7 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [ConditionalFact] // Mono issue - https://github.com/aspnet/External/issues/18 [FrameworkSkipCondition(RuntimeFrameworks.Mono)] - public async Task DerivedClassLevelAttribute_OveridesBaseClassLevel() + public async Task DerivedClassLevelAttribute_OverridesBaseClassLevel() { // Arrange var input = " - + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html index ffa6be6fdf..b0a2e41eb6 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html @@ -96,7 +96,7 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs index 7a49359cd3..9eb3af091e 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs @@ -2659,7 +2659,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests // This covers the case where a key is present, but has an empty value. The type converter // will report an error. [Fact] - public async Task MutableObjectModelBinder_BindsPOCO_TypeConvertedPropertyNonConvertableValue_GetsError() + public async Task MutableObjectModelBinder_BindsPOCO_TypeConvertedPropertyNonConvertibleValue_GetsError() { // Arrange var parameter = new ParameterDescriptor() diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs index 420939c66b..0999087bec 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs @@ -241,7 +241,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests } [Fact] - public async Task BindParameter_NonConvertableValue_GetsError() + public async Task BindParameter_NonConvertibleValue_GetsError() { // Arrange var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); @@ -290,7 +290,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests } [Fact] - public async Task BindParameter_NonConvertableValue_GetsCustomErrorMessage() + public async Task BindParameter_NonConvertibleValue_GetsCustomErrorMessage() { // Arrange var parameterType = typeof(int); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs index c15912dede..008435da3c 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs @@ -110,7 +110,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal private class TestPageWithDerivedModel : Page { - public DeriviedModel Model => null; + public DerivedModel Model => null; public override Task ExecuteAsync() =>throw new NotImplementedException(); } @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } [Authorize(Policy = "Derived")] - private class DeriviedModel : BaseModel + private class DerivedModel : BaseModel { public virtual void OnGet() { diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs index 489c78bd05..d6789c5376 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs @@ -361,7 +361,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Fact] - public async Task ProcessAsync_DoesNotUseModelFromViewdata_IfModelExpressionEvalulatesToNull() + public async Task ProcessAsync_DoesNotUseModelFromViewdata_IfModelExpressionEvaluatesToNull() { // Arrange var bufferScope = new TestViewBufferScope(); @@ -455,7 +455,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers } [Fact] - public async Task ProcessAsync_UsesModelOnViewContextViewData_WhenModelExpresionIsNull() + public async Task ProcessAsync_UsesModelOnViewContextViewData_WhenModelExpressionIsNull() { // Arrange var bufferScope = new TestViewBufferScope(); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs index fec670ba58..57730851cc 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs @@ -98,7 +98,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers [Theory] [MemberData(nameof(ProcessAsync_GeneratesExpectedOutput_WithNoErrorsData))] - public async Task ProcessAsync_SuppressesOutput_IfClientSideValiationDisabled_WithNoErrorsData( + public async Task ProcessAsync_SuppressesOutput_IfClientSideValidationDisabled_WithNoErrorsData( ModelStateDictionary modelStateDictionary) { // Arrange diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs index 73e3b0cc08..e9526d1d62 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } [Fact] - public void ObjectTemplate_HonoursHideSurroundingHtml() + public void ObjectTemplate_HonorsHideSurroundingHtml() { // Arrange var expected = @@ -231,7 +231,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal } [Fact] - public void HiddenInputTemplate_HonoursHideSurroundingHtml() + public void HiddenInputTemplate_HonorsHideSurroundingHtml() { // Arrange var model = "Model string"; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs index a8b6659a91..ad94d3bf9e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs @@ -374,7 +374,7 @@ Environment.NewLine; } [Fact] - public void ObjectTemplate_HonoursHideSurroundingHtml() + public void ObjectTemplate_HonorsHideSurroundingHtml() { // Arrange var expected = @@ -470,7 +470,7 @@ Environment.NewLine; } [Fact] - public void HiddenInputTemplate_HonoursHideSurroundingHtml() + public void HiddenInputTemplate_HonorsHideSurroundingHtml() { // Arrange var expected = ""; diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs index 48f0726884..1ab02127a9 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs @@ -54,7 +54,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal Assert.Collection( result.OrderBy(p => p.Key), property => Assert.Equal(nameof(BaseController.BaseProperty), property.PropertyInfo.Name), - property => Assert.Equal(nameof(DerivedController.DeriviedProperty), property.PropertyInfo.Name)); + property => Assert.Equal(nameof(DerivedController.DerivedProperty), property.PropertyInfo.Name)); } [Fact] @@ -90,7 +90,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal public class DerivedController : BaseController { [ViewData] - public string DeriviedProperty { get; set; } + public string DerivedProperty { get; set; } } public class PropertyWithKeyController diff --git a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs index d29b1a2f2b..a9630cba1f 100644 --- a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs +++ b/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs @@ -34,10 +34,10 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public async Task GetAttributes_OnNonOverriddenMethod_ReturnsAllAttributesOnCurrentAction() { // Arrange - var compilation = await GetCompilation("GetAttributes_WithoutMethodOverridding"); + var compilation = await GetCompilation("GetAttributes_WithoutMethodOverriding"); var attribute = compilation.GetTypeByMetadataName(typeof(ProducesResponseTypeAttribute).FullName); - var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithoutMethodOverridding)}"); - var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithoutMethodOverridding.Method)).First(); + var testClass = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetAttributes_WithoutMethodOverriding)}"); + var method = (IMethodSymbol)testClass.GetMembers(nameof(GetAttributes_WithoutMethodOverriding.Method)).First(); // Act var attributes = CodeAnalysisExtensions.GetAttributes(method, attribute, inherit: true); diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs similarity index 70% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs rename to test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs index 653abeb19b..79c2be699b 100644 --- a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverridding.cs +++ b/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs @@ -1,6 +1,6 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers { - public class GetAttributes_WithoutMethodOverridding + public class GetAttributes_WithoutMethodOverriding { [ProducesResponseType(201)] public void Method() { } diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs index d29a11bc5f..be93692211 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs @@ -5,8 +5,8 @@ using Microsoft.AspNetCore.Mvc; namespace ApiExplorerWebSite { - [Route("ApiExplorerVisbilityDisabledByConvention")] - public class ApiExplorerVisbilityDisabledByConventionController : Controller + [Route("ApiExplorerVisibilityDisabledByConvention")] + public class ApiExplorerVisibilityDisabledByConventionController : Controller { [HttpGet] public void Get() diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs index 7b674cb022..055e91e0ea 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs @@ -5,8 +5,8 @@ using Microsoft.AspNetCore.Mvc; namespace ApiExplorerWebSite { - [Route("ApiExplorerVisbilityEnabledByConvention")] - public class ApiExplorerVisbilityEnabledByConventionController : Controller + [Route("ApiExplorerVisibilityEnabledByConvention")] + public class ApiExplorerVisibilityEnabledByConventionController : Controller { [HttpGet] public void Get() diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/test/WebSites/ApiExplorerWebSite/Startup.cs index f5f96abfd5..08965fe0bf 100644 --- a/test/WebSites/ApiExplorerWebSite/Startup.cs +++ b/test/WebSites/ApiExplorerWebSite/Startup.cs @@ -28,7 +28,7 @@ namespace ApiExplorerWebSite options.Conventions.Add(new ApiExplorerVisibilityEnabledConvention()); options.Conventions.Add(new ApiExplorerVisibilityDisabledConvention( - typeof(ApiExplorerVisbilityDisabledByConventionController))); + typeof(ApiExplorerVisibilityDisabledByConventionController))); options.Conventions.Add(new ApiExplorerInboundOutboundConvention( typeof(ApiExplorerInboundOutBoundController))); options.Conventions.Add(new ApiExplorerRouteChangeConvention(wellKnownChangeToken)); diff --git a/test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs b/test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs similarity index 90% rename from test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs rename to test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs index 27a2643cfc..2d428f9f4f 100644 --- a/test/WebSites/FormatterWebSite/Controllers/PolymorhpicPropertyBindingController.cs +++ b/test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs @@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Mvc; namespace FormatterWebSite.Controllers { - public class PolymorhpicPropertyBindingController : ControllerBase + public class PolymorphicPropertyBindingController : ControllerBase { [FromBody] public IModel Person { get; set; } diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml index ffa657373f..dbd8dd6272 100644 --- a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml +++ b/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml @@ -157,7 +157,7 @@ asp-fallback-test-property="visibility" asp-fallback-test-value="hidden" /> - + Date: Wed, 5 Sep 2018 16:43:25 -0700 Subject: [PATCH 237/316] Refactor DefaultPageApplicationModel to use conventions --- .../SymbolNames.cs | 2 +- .../ApiSymbolNames.cs | 2 +- .../ApiBehaviorApplicationModelProvider.cs | 127 ++ ...ApiConventionApplicationModelConvention.cs | 88 + .../ApiVisibilityConvention.cs | 27 + .../ClientErrorResultFilterConvention.cs | 36 + ...onstraintForFormFileParameterConvention.cs | 55 + .../InferParameterBindingInfoConvention.cs | 170 ++ .../InvalidModelStateFilterConvention.cs | 35 + .../{Internal => }/IApiBehaviorMetadata.cs | 4 +- .../Infrastructure/ClientErrorResultFilter.cs | 11 +- .../ClientErrorResultFilterFactory.cs | 22 + .../Infrastructure/ModelStateInvalidFilter.cs | 13 +- .../ModelStateInvalidFilterFactory.cs | 26 + .../ApiBehaviorApplicationModelProvider.cs | 326 ---- .../ProducesErrorResponseTypeAttribute.cs | 2 +- .../breakingchanges.netcore.json | 6 + .../ActionModelTest.cs | 0 ...ApiBehaviorApplicationModelProviderTest.cs | 199 +++ ...onventionApplicationModelConventionTest.cs | 221 +++ .../ApiVisibilityConventionTest.cs | 63 + .../ClientErrorResultFilterConventionTest.cs | 38 + ...raintForFormFileParameterConventionTest.cs | 104 ++ ...InferParameterBindingInfoConventionTest.cs | 986 ++++++++++ .../InvalidModelStateFilterConventionTest.cs | 39 + .../AttributeRouteModelTests.cs | 0 .../ControllerModelTest.cs | 0 .../ParameterModelTest.cs | 0 .../PropertyModelTest.cs | 0 ...ApiBehaviorApplicationModelProviderTest.cs | 1591 ----------------- 30 files changed, 2265 insertions(+), 1928 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs rename src/Microsoft.AspNetCore.Mvc.Core/{Internal => }/IApiBehaviorMetadata.cs (80%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/ActionModelTest.cs (100%) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/AttributeRouteModelTests.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/ControllerModelTest.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/ParameterModelTest.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/PropertyModelTest.cs (100%) delete mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs index ad152657df..42d3b18af4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs @@ -29,7 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Analyzers public const string HtmlHelperPartialExtensionsType = "Microsoft.AspNetCore.Mvc.Rendering.HtmlHelperPartialExtensions"; - public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; + public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.IApiBehaviorMetadata"; public const string IBinderTypeProviderMetadata = "Microsoft.AspNetCore.Mvc.ModelBinding.IBinderTypeProviderMetadata"; diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs index 563c3ad570..135d742feb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public const string DefaultStatusCodeAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.DefaultStatusCodeAttribute"; - public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata"; + public const string IApiBehaviorMetadata = "Microsoft.AspNetCore.Mvc.IApiBehaviorMetadata"; public const string IActionResult = "Microsoft.AspNetCore.Mvc.IActionResult"; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs new file mode 100644 index 0000000000..54f24f8bb2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + internal class ApiBehaviorApplicationModelProvider : IApplicationModelProvider + { + public ApiBehaviorApplicationModelProvider( + IOptions apiBehaviorOptions, + IModelMetadataProvider modelMetadataProvider, + IClientErrorFactory clientErrorFactory, + ILoggerFactory loggerFactory) + { + var options = apiBehaviorOptions.Value; + + ActionModelConventions = new List() + { + new ApiVisibilityConvention(), + }; + + if (!options.SuppressMapClientErrors) + { + ActionModelConventions.Add(new ClientErrorResultFilterConvention()); + } + + if (!options.SuppressModelStateInvalidFilter) + { + ActionModelConventions.Add(new InvalidModelStateFilterConvention()); + } + + if (!options.SuppressConsumesConstraintForFormFileParameters) + { + ActionModelConventions.Add(new ConsumesConstraintForFormFileParameterConvention()); + } + + var defaultErrorType = options.SuppressMapClientErrors ? typeof(void) : typeof(ProblemDetails); + var defaultErrorTypeAttribute = new ProducesErrorResponseTypeAttribute(defaultErrorType); + ActionModelConventions.Add(new ApiConventionApplicationModelConvention(defaultErrorTypeAttribute)); + + var inferParameterBindingInfoConvention = new InferParameterBindingInfoConvention(modelMetadataProvider) + { + SuppressInferBindingSourcesForParameters = options.SuppressInferBindingSourcesForParameters + }; + ControllerModelConventions = new List + { + inferParameterBindingInfoConvention, + }; + } + + /// + /// Order is set to execute after the and allow any other user + /// that configure routing to execute. + /// + public int Order => -1000 + 100; + + public List ActionModelConventions { get; } + + public List ControllerModelConventions { get; } + + public void OnProvidersExecuted(ApplicationModelProviderContext context) + { + } + + public void OnProvidersExecuting(ApplicationModelProviderContext context) + { + foreach (var controller in context.Result.Controllers) + { + if (!controller.Attributes.OfType().Any()) + { + continue; + } + + foreach (var action in controller.Actions) + { + // Ensure ApiController is set up correctly + EnsureActionIsAttributeRouted(action); + + foreach (var convention in ActionModelConventions) + { + convention.Apply(action); + } + } + + foreach (var convention in ControllerModelConventions) + { + convention.Apply(controller); + } + } + } + + private static void EnsureActionIsAttributeRouted(ActionModel actionModel) + { + if (!IsAttributeRouted(actionModel.Controller.Selectors) && + !IsAttributeRouted(actionModel.Selectors)) + { + // Require attribute routing with controllers annotated with ApiControllerAttribute + var message = Resources.FormatApiController_AttributeRouteRequired( + actionModel.DisplayName, + nameof(ApiControllerAttribute)); + throw new InvalidOperationException(message); + } + + bool IsAttributeRouted(IList selectorModel) + { + for (var i = 0; i < selectorModel.Count; i++) + { + if (selectorModel[i].AttributeRouteModel != null) + { + return true; + } + } + + return false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs new file mode 100644 index 0000000000..9311ffc8e0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApiExplorer; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that discovers + /// + /// from applied or . + /// that applies to the action. + /// + /// + public class ApiConventionApplicationModelConvention : IActionModelConvention + { + /// + /// Initializes a new instance of . + /// + /// The error type to be used. Use + /// when no default error type is to be inferred. + /// + public ApiConventionApplicationModelConvention(ProducesErrorResponseTypeAttribute defaultErrorResponseType) + { + DefaultErrorResponseType = defaultErrorResponseType ?? throw new ArgumentNullException(nameof(defaultErrorResponseType)); + } + + /// + /// Gets the default that is associated with an action + /// when no attribute is discovered. + /// + public ProducesErrorResponseTypeAttribute DefaultErrorResponseType { get; } + + public void Apply(ActionModel action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (!ShouldApply(action)) + { + return; + } + + DiscoverApiConvention(action); + DiscoverErrorResponseType(action); + } + + protected virtual bool ShouldApply(ActionModel action) => true; + + private static void DiscoverApiConvention(ActionModel action) + { + if (action.Filters.OfType().Any()) + { + // If an action already has providers, don't discover any from conventions. + return; + } + + var controller = action.Controller; + var apiConventionAttributes = controller.Attributes.OfType().ToArray(); + if (apiConventionAttributes.Length == 0) + { + var controllerAssembly = controller.ControllerType.Assembly; + apiConventionAttributes = controllerAssembly.GetCustomAttributes().ToArray(); + } + + if (ApiConventionResult.TryGetApiConvention(action.ActionMethod, apiConventionAttributes, out var result)) + { + action.Properties[typeof(ApiConventionResult)] = result; + } + } + + private void DiscoverErrorResponseType(ActionModel action) + { + var errorTypeAttribute = + action.Attributes.OfType().FirstOrDefault() ?? + action.Controller.Attributes.OfType().FirstOrDefault() ?? + action.Controller.ControllerType.Assembly.GetCustomAttribute() ?? + DefaultErrorResponseType; + + action.Properties[typeof(ProducesErrorResponseTypeAttribute)] = errorTypeAttribute; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs new file mode 100644 index 0000000000..fb269048cf --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs @@ -0,0 +1,27 @@ +// 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.ApplicationModels +{ + /// + /// A that sets Api Explorer visibility. + /// + public class ApiVisibilityConvention : IActionModelConvention + { + public void Apply(ActionModel action) + { + if (!ShouldApply(action)) + { + return; + } + + if (action.Controller.ApiExplorer.IsVisible == null && action.ApiExplorer.IsVisible == null) + { + // Enable ApiExplorer for the action if it wasn't already explicitly configured. + action.ApiExplorer.IsVisible = true; + } + } + + protected virtual bool ShouldApply(ActionModel action) => true; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs new file mode 100644 index 0000000000..2b0cc744df --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that adds a + /// to that transforms . + /// + public class ClientErrorResultFilterConvention : IActionModelConvention + { + private readonly ClientErrorResultFilterFactory _filterFactory = new ClientErrorResultFilterFactory(); + + public void Apply(ActionModel action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (!ShouldApply(action)) + { + return; + } + + + action.Filters.Add(_filterFactory); + } + + protected virtual bool ShouldApply(ActionModel action) => true; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs new file mode 100644 index 0000000000..1f1f2cf29a --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that adds a with multipart/form-data + /// to controllers containing form file () parameters. + /// + public class ConsumesConstraintForFormFileParameterConvention : IActionModelConvention + { + public void Apply(ActionModel action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (!ShouldApply(action)) + { + return; + } + + AddMultipartFormDataConsumesAttribute(action); + } + + protected virtual bool ShouldApply(ActionModel action) => true; + + // Internal for unit testing + internal void AddMultipartFormDataConsumesAttribute(ActionModel action) + { + // Add a ConsumesAttribute if the request does not explicitly specify one. + if (action.Filters.OfType().Any()) + { + return; + } + + foreach (var parameter in action.Parameters) + { + var bindingSource = parameter.BindingInfo?.BindingSource; + if (bindingSource == BindingSource.FormFile) + { + // If an controller accepts files, it must accept multipart/form-data. + action.Filters.Add(new ConsumesAttribute("multipart/form-data")); + return; + } + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs new file mode 100644 index 0000000000..ba7fe297d2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs @@ -0,0 +1,170 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Routing.Template; +using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// A that + /// + /// infers binding sources for parameters + /// for bound properties and parameters. + /// + /// + public class InferParameterBindingInfoConvention : IControllerModelConvention + { + private readonly IModelMetadataProvider _modelMetadataProvider; + + public InferParameterBindingInfoConvention( + IModelMetadataProvider modelMetadataProvider) + { + _modelMetadataProvider = modelMetadataProvider ?? throw new ArgumentNullException(nameof(modelMetadataProvider)); + } + + /// + /// Gets or sets a value that determines if model binding sources are inferred for action parameters on controllers is suppressed. + /// + public bool SuppressInferBindingSourcesForParameters { get; set; } + + protected virtual bool ShouldApply(ControllerModel controller) => true; + + public void Apply(ControllerModel controller) + { + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + if (!ShouldApply(controller)) + { + return; + } + + InferBoundPropertyModelPrefixes(controller); + + foreach (var action in controller.Actions) + { + InferParameterBindingSources(action); + InferParameterModelPrefixes(action); + } + } + + internal void InferParameterBindingSources(ActionModel action) + { + var inferredBindingSources = new BindingSource[action.Parameters.Count]; + + for (var i = 0; i < action.Parameters.Count; i++) + { + var parameter = action.Parameters[i]; + var bindingSource = parameter.BindingInfo?.BindingSource; + if (bindingSource == null) + { + bindingSource = InferBindingSourceForParameter(parameter); + + parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo(); + parameter.BindingInfo.BindingSource = bindingSource; + } + } + + var fromBodyParameters = action.Parameters.Where(p => p.BindingInfo.BindingSource == BindingSource.Body).ToList(); + if (fromBodyParameters.Count > 1) + { + var parameters = string.Join(Environment.NewLine, fromBodyParameters.Select(p => p.DisplayName)); + var message = Resources.FormatApiController_MultipleBodyParametersFound( + action.DisplayName, + nameof(FromQueryAttribute), + nameof(FromRouteAttribute), + nameof(FromBodyAttribute)); + + message += Environment.NewLine + parameters; + throw new InvalidOperationException(message); + } + } + + // Internal for unit testing. + internal BindingSource InferBindingSourceForParameter(ParameterModel parameter) + { + if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName)) + { + return BindingSource.Path; + } + + var bindingSource = IsComplexTypeParameter(parameter) ? + BindingSource.Body : + BindingSource.Query; + + return bindingSource; + } + + // For any complex types that are bound from value providers, set the prefix + // to the empty prefix by default. This makes binding much more predictable + // and describable via ApiExplorer + internal void InferBoundPropertyModelPrefixes(ControllerModel controllerModel) + { + foreach (var property in controllerModel.ControllerProperties) + { + if (property.BindingInfo != null && + property.BindingInfo.BinderModelName == null && + property.BindingInfo.BindingSource != null && + !property.BindingInfo.BindingSource.IsGreedy) + { + var metadata = _modelMetadataProvider.GetMetadataForProperty( + controllerModel.ControllerType, + property.PropertyInfo.Name); + if (metadata.IsComplexType && !metadata.IsCollectionType) + { + property.BindingInfo.BinderModelName = string.Empty; + } + } + } + } + + internal void InferParameterModelPrefixes(ActionModel action) + { + foreach (var parameter in action.Parameters) + { + var bindingInfo = parameter.BindingInfo; + if (bindingInfo?.BindingSource != null && + bindingInfo.BinderModelName == null && + !bindingInfo.BindingSource.IsGreedy && + IsComplexTypeParameter(parameter)) + { + parameter.BindingInfo.BinderModelName = string.Empty; + } + } + } + + private bool ParameterExistsInAnyRoute(ActionModel action, string parameterName) + { + foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(action)) + { + if (route == null) + { + continue; + } + + var parsedTemplate = TemplateParser.Parse(route.Template); + if (parsedTemplate.GetParameter(parameterName) != null) + { + return true; + } + } + + return false; + } + + private bool IsComplexTypeParameter(ParameterModel parameter) + { + // No need for information from attributes on the parameter. Just use its type. + var metadata = _modelMetadataProvider + .GetMetadataForType(parameter.ParameterInfo.ParameterType); + return metadata.IsComplexType && !metadata.IsCollectionType; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs new file mode 100644 index 0000000000..6d15ae2d58 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that adds a + /// to that responds to invalid + /// + public class InvalidModelStateFilterConvention : IActionModelConvention + { + private readonly ModelStateInvalidFilterFactory _filterFactory = new ModelStateInvalidFilterFactory(); + + public void Apply(ActionModel action) + { + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (!ShouldApply(action)) + { + return; + } + + action.Filters.Add(_filterFactory); + } + + protected virtual bool ShouldApply(ActionModel action) => true; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs similarity index 80% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.cs rename to src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs index b73c1ba56e..41a053ebed 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IApiBehaviorMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs @@ -3,13 +3,13 @@ using Microsoft.AspNetCore.Mvc.Filters; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc { /// /// An interface for . See /// for details. /// - public interface IApiBehaviorMetadata : IFilterMetadata + internal interface IApiBehaviorMetadata : IFilterMetadata { } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs index a212c50ea4..fd5911ea07 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs @@ -10,14 +10,10 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { internal class ClientErrorResultFilter : IAlwaysRunResultFilter, IOrderedFilter { + internal const int FilterOrder = -2000; private readonly IClientErrorFactory _clientErrorFactory; private readonly ILogger _logger; - /// - /// Gets the filter order. Defaults to -2000 so that it runs early. - /// - public int Order => -2000; - public ClientErrorResultFilter( IClientErrorFactory clientErrorFactory, ILogger logger) @@ -26,6 +22,11 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } + /// + /// Gets the filter order. Defaults to -2000 so that it runs early. + /// + public int Order => FilterOrder; + public void OnResultExecuted(ResultExecutedContext context) { } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs new file mode 100644 index 0000000000..e4665fcc5d --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs @@ -0,0 +1,22 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal sealed class ClientErrorResultFilterFactory : IFilterFactory, IOrderedFilter + { + public int Order => ClientErrorResultFilter.FilterOrder; + + public bool IsReusable => true; + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var resultFilter = ActivatorUtilities.CreateInstance(serviceProvider); + return resultFilter; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs index 43aa96daec..c535bb118b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.Logging; @@ -15,12 +16,22 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// public class ModelStateInvalidFilter : IActionFilter, IOrderedFilter { + internal const int FilterOrder = -2000; + private readonly ApiBehaviorOptions _apiBehaviorOptions; private readonly ILogger _logger; public ModelStateInvalidFilter(ApiBehaviorOptions apiBehaviorOptions, ILogger logger) { _apiBehaviorOptions = apiBehaviorOptions ?? throw new ArgumentNullException(nameof(apiBehaviorOptions)); + if (!_apiBehaviorOptions.SuppressModelStateInvalidFilter && _apiBehaviorOptions.InvalidModelStateResponseFactory == null) + { + throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( + typeof(ApiBehaviorOptions), + nameof(ApiBehaviorOptions.InvalidModelStateResponseFactory))); + } + + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } @@ -39,7 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure /// Look at for more detailed info. /// /// - public int Order => -2000; + public int Order => FilterOrder; /// public bool IsReusable => true; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs new file mode 100644 index 0000000000..3f5c81dc98 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.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; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + internal sealed class ModelStateInvalidFilterFactory : IFilterFactory, IOrderedFilter + { + public int Order => ModelStateInvalidFilter.FilterOrder; + + public bool IsReusable => true; + + public IFilterMetadata CreateInstance(IServiceProvider serviceProvider) + { + var options = serviceProvider.GetRequiredService>(); + var loggerFactory = serviceProvider.GetRequiredService(); + + return new ModelStateInvalidFilter(options.Value, loggerFactory.CreateLogger()); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs deleted file mode 100644 index 9eeb83769a..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorApplicationModelProvider.cs +++ /dev/null @@ -1,326 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Diagnostics; -using System.Linq; -using System.Reflection; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Routing.Template; -using Microsoft.Extensions.Logging; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class ApiBehaviorApplicationModelProvider : IApplicationModelProvider - { - private readonly ProducesErrorResponseTypeAttribute DefaultErrorType = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails)); - private readonly ApiBehaviorOptions _apiBehaviorOptions; - private readonly IModelMetadataProvider _modelMetadataProvider; - private readonly ModelStateInvalidFilter _modelStateInvalidFilter; - private readonly ClientErrorResultFilter _clientErrorResultFilter; - private readonly ILogger _logger; - - public ApiBehaviorApplicationModelProvider( - IOptions apiBehaviorOptions, - IModelMetadataProvider modelMetadataProvider, - IClientErrorFactory clientErrorFactory, - ILoggerFactory loggerFactory) - { - _apiBehaviorOptions = apiBehaviorOptions.Value; - _modelMetadataProvider = modelMetadataProvider; - _logger = loggerFactory.CreateLogger(); - - if (!_apiBehaviorOptions.SuppressModelStateInvalidFilter && _apiBehaviorOptions.InvalidModelStateResponseFactory == null) - { - throw new ArgumentException(Resources.FormatPropertyOfTypeCannotBeNull( - typeof(ApiBehaviorOptions), - nameof(ApiBehaviorOptions.InvalidModelStateResponseFactory))); - } - - _modelStateInvalidFilter = new ModelStateInvalidFilter( - apiBehaviorOptions.Value, - loggerFactory.CreateLogger()); - - _clientErrorResultFilter = new ClientErrorResultFilter( - clientErrorFactory, - loggerFactory.CreateLogger()); - } - - /// - /// Order is set to execute after the and allow any other user - /// that configure routing to execute. - /// - public int Order => -1000 + 100; - - public void OnProvidersExecuted(ApplicationModelProviderContext context) - { - } - - public void OnProvidersExecuting(ApplicationModelProviderContext context) - { - foreach (var controllerModel in context.Result.Controllers) - { - var isApiController = controllerModel.Attributes.OfType().Any(); - if (isApiController && - controllerModel.ApiExplorer.IsVisible == null) - { - // Enable ApiExplorer for the controller if it wasn't already explicitly configured. - controllerModel.ApiExplorer.IsVisible = true; - } - - if (isApiController) - { - InferBoundPropertyModelPrefixes(controllerModel); - } - - var controllerHasSelectorModel = controllerModel.Selectors.Any(s => s.AttributeRouteModel != null); - var conventions = controllerModel.Attributes.OfType().ToArray(); - if (conventions.Length == 0) - { - var controllerAssembly = controllerModel.ControllerType.Assembly; - conventions = controllerAssembly.GetCustomAttributes().ToArray(); - } - - foreach (var actionModel in controllerModel.Actions) - { - if (!isApiController && !actionModel.Attributes.OfType().Any()) - { - continue; - } - - EnsureActionIsAttributeRouted(controllerHasSelectorModel, actionModel); - - AddInvalidModelStateFilter(actionModel); - - AddClientErrorFilter(actionModel); - - InferParameterBindingSources(actionModel); - - InferParameterModelPrefixes(actionModel); - - AddMultipartFormDataConsumesAttribute(actionModel); - - DiscoverApiConvention(actionModel, conventions); - - DiscoverErrorResponseType(actionModel); - } - } - } - - // Internal for unit testing - internal void AddMultipartFormDataConsumesAttribute(ActionModel actionModel) - { - if (_apiBehaviorOptions.SuppressConsumesConstraintForFormFileParameters) - { - return; - } - - // Add a ConsumesAttribute if the request does not explicitly specify one. - if (actionModel.Filters.OfType().Any()) - { - return; - } - - foreach (var parameter in actionModel.Parameters) - { - var bindingSource = parameter.BindingInfo?.BindingSource; - if (bindingSource == BindingSource.FormFile) - { - // If an action accepts files, it must accept multipart/form-data. - actionModel.Filters.Add(new ConsumesAttribute("multipart/form-data")); - } - } - } - - private static void EnsureActionIsAttributeRouted(bool controllerHasSelectorModel, ActionModel actionModel) - { - if (!controllerHasSelectorModel && !actionModel.Selectors.Any(s => s.AttributeRouteModel != null)) - { - // Require attribute routing with controllers annotated with ApiControllerAttribute - var message = Resources.FormatApiController_AttributeRouteRequired( - actionModel.DisplayName, - nameof(ApiControllerAttribute)); - throw new InvalidOperationException(message); - } - } - - private void AddInvalidModelStateFilter(ActionModel actionModel) - { - if (_apiBehaviorOptions.SuppressModelStateInvalidFilter) - { - return; - } - - Debug.Assert(_apiBehaviorOptions.InvalidModelStateResponseFactory != null); - actionModel.Filters.Add(_modelStateInvalidFilter); - } - - private void AddClientErrorFilter(ActionModel actionModel) - { - if (_apiBehaviorOptions.SuppressMapClientErrors) - { - return; - } - - actionModel.Filters.Add(_clientErrorResultFilter); - } - - // Internal for unit testing - internal void InferParameterBindingSources(ActionModel actionModel) - { - if (_modelMetadataProvider == null || _apiBehaviorOptions.SuppressInferBindingSourcesForParameters) - { - return; - } - var inferredBindingSources = new BindingSource[actionModel.Parameters.Count]; - - for (var i = 0; i < actionModel.Parameters.Count; i++) - { - var parameter = actionModel.Parameters[i]; - var bindingSource = parameter.BindingInfo?.BindingSource; - if (bindingSource == null) - { - bindingSource = InferBindingSourceForParameter(parameter); - - parameter.BindingInfo = parameter.BindingInfo ?? new BindingInfo(); - parameter.BindingInfo.BindingSource = bindingSource; - } - } - - var fromBodyParameters = actionModel.Parameters.Where(p => p.BindingInfo.BindingSource == BindingSource.Body).ToList(); - if (fromBodyParameters.Count > 1) - { - var parameters = string.Join(Environment.NewLine, fromBodyParameters.Select(p => p.DisplayName)); - var message = Resources.FormatApiController_MultipleBodyParametersFound( - actionModel.DisplayName, - nameof(FromQueryAttribute), - nameof(FromRouteAttribute), - nameof(FromBodyAttribute)); - - message += Environment.NewLine + parameters; - throw new InvalidOperationException(message); - } - } - - // For any complex types that are bound from value providers, set the prefix - // to the empty prefix by default. This makes binding much more predictable - // and describable via ApiExplorer - - // internal for testing - internal void InferBoundPropertyModelPrefixes(ControllerModel controllerModel) - { - foreach (var property in controllerModel.ControllerProperties) - { - if (property.BindingInfo != null && - property.BindingInfo.BinderModelName == null && - property.BindingInfo.BindingSource != null && - !property.BindingInfo.BindingSource.IsGreedy) - { - var metadata = _modelMetadataProvider.GetMetadataForProperty( - controllerModel.ControllerType, - property.PropertyInfo.Name); - if (metadata.IsComplexType && !metadata.IsCollectionType) - { - property.BindingInfo.BinderModelName = string.Empty; - } - } - } - } - - // internal for testing - internal void InferParameterModelPrefixes(ActionModel actionModel) - { - foreach (var parameter in actionModel.Parameters) - { - var bindingInfo = parameter.BindingInfo; - if (bindingInfo?.BindingSource != null && - bindingInfo.BinderModelName == null && - !bindingInfo.BindingSource.IsGreedy && - IsComplexTypeParameter(parameter)) - { - parameter.BindingInfo.BinderModelName = string.Empty; - } - } - } - - // Internal for unit testing. - internal BindingSource InferBindingSourceForParameter(ParameterModel parameter) - { - if (ParameterExistsInAnyRoute(parameter.Action, parameter.ParameterName)) - { - return BindingSource.Path; - } - - var bindingSource = IsComplexTypeParameter(parameter) ? - BindingSource.Body : - BindingSource.Query; - - return bindingSource; - } - - internal static void DiscoverApiConvention(ActionModel actionModel, ApiConventionTypeAttribute[] apiConventionAttributes) - { - if (actionModel.Filters.OfType().Any()) - { - // If an action already has providers, don't discover any from conventions. - return; - } - - if (ApiConventionResult.TryGetApiConvention(actionModel.ActionMethod, apiConventionAttributes, out var result)) - { - actionModel.Properties[typeof(ApiConventionResult)] = result; - } - } - - internal void DiscoverErrorResponseType(ActionModel actionModel) - { - var errorTypeAttribute = - actionModel.Attributes.OfType().FirstOrDefault() ?? - actionModel.Controller.Attributes.OfType().FirstOrDefault() ?? - actionModel.Controller.ControllerType.Assembly.GetCustomAttribute(); - - if (!_apiBehaviorOptions.SuppressMapClientErrors) - { - // If ClientErrorFactory is being used and the application does not supply a error response type, assume ProblemDetails. - errorTypeAttribute = errorTypeAttribute ?? DefaultErrorType; - } - - if (errorTypeAttribute != null) - { - actionModel.Properties[typeof(ProducesErrorResponseTypeAttribute)] = errorTypeAttribute; - } - } - - private bool ParameterExistsInAnyRoute(ActionModel actionModel, string parameterName) - { - foreach (var (route, _, _) in ActionAttributeRouteModel.GetAttributeRoutes(actionModel)) - { - if (route == null) - { - continue; - } - - var parsedTemplate = TemplateParser.Parse(route.Template); - if (parsedTemplate.GetParameter(parameterName) != null) - { - return true; - } - } - - return false; - } - - private bool IsComplexTypeParameter(ParameterModel parameter) - { - // No need for information from attributes on the parameter. Just use its type. - var metadata = _modelMetadataProvider - .GetMetadataForType(parameter.ParameterInfo.ParameterType); - return metadata.IsComplexType && !metadata.IsCollectionType; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs index f74a826e93..b9c579cea8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs @@ -24,7 +24,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Initializes a new instance of . /// - /// The error type. + /// The error type. Use to indicate the absence of a default error type. public ProducesErrorResponseTypeAttribute(Type type) { Type = type ?? throw new ArgumentNullException(nameof(type)); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json b/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json new file mode 100644 index 0000000000..3fd0e6ddff --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json @@ -0,0 +1,6 @@ +[ + { + "TypeId": "public class Microsoft.AspNetCore.Mvc.ApiControllerAttribute : Microsoft.AspNetCore.Mvc.ControllerAttribute, Microsoft.AspNetCore.Mvc.Internal.IApiBehaviorMetadata", + "Kind": "Removal" + } +] \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ActionModelTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs new file mode 100644 index 0000000000..1d00e52dc2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs @@ -0,0 +1,199 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class ApiBehaviorApplicationModelProviderTest + { + [Fact] + public void OnProvidersExecuting_ThrowsIfControllerWithAttribute_HasActionsWithoutAttributeRouting() + { + // Arrange + var actionName = $"{typeof(TestApiController).FullName}.{nameof(TestApiController.TestAction)} ({typeof(TestApiController).Assembly.GetName().Name})"; + var expected = $"Action '{actionName}' does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed."; + + var controllerModel = new ControllerModel(typeof(TestApiController).GetTypeInfo(), new[] { new ApiControllerAttribute() }); + var method = typeof(TestApiController).GetMethod(nameof(TestApiController.TestAction)); + var actionModel = new ActionModel(method, Array.Empty()) + { + Controller = controllerModel, + }; + controllerModel.Actions.Add(actionModel); + + var context = new ApplicationModelProviderContext(new[] { controllerModel.ControllerType }); + context.Result.Controllers.Add(controllerModel); + + var provider = GetProvider(); + + // Act & Assert + var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void OnProvidersExecuting_AppliesConventions() + { + // Arrange + var controllerModel = new ControllerModel(typeof(TestApiController).GetTypeInfo(), new[] { new ApiControllerAttribute() }) + { + Selectors = { new SelectorModel { AttributeRouteModel = new AttributeRouteModel() } }, + }; + + var method = typeof(TestApiController).GetMethod(nameof(TestApiController.TestAction)); + + var actionModel = new ActionModel(method, Array.Empty()) + { + Controller = controllerModel, + }; + controllerModel.Actions.Add(actionModel); + + var parameter = method.GetParameters()[0]; + var parameterModel = new ParameterModel(parameter, Array.Empty()) + { + Action = actionModel, + }; + actionModel.Parameters.Add(parameterModel); + + var context = new ApplicationModelProviderContext(new[] { controllerModel.ControllerType }); + context.Result.Controllers.Add(controllerModel); + + var provider = GetProvider(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + // Verify some of the side-effects of executing API behavior conventions. + Assert.True(actionModel.ApiExplorer.IsVisible); + Assert.NotEmpty(actionModel.Filters.OfType()); + Assert.NotEmpty(actionModel.Filters.OfType()); + Assert.Equal(BindingSource.Body, parameterModel.BindingInfo.BindingSource); + } + + [Fact] + public void Constructor_SetsUpConventions() + { + // Arrange + var provider = GetProvider(); + + // Act & Assert + Assert.Collection( + provider.ActionModelConventions, + c => Assert.IsType(c), + c => Assert.IsType(c), + c => Assert.IsType(c), + c => Assert.IsType(c), + c => + { + var convention = Assert.IsType(c); + Assert.Equal(typeof(ProblemDetails), convention.DefaultErrorResponseType.Type); + }); + + Assert.Collection( + provider.ControllerModelConventions, + c => + { + var convention = Assert.IsType(c); + Assert.False(convention.SuppressInferBindingSourcesForParameters); + }); + } + + [Fact] + public void Constructor_DoesNotAddClientErrorResultFilterConvention_IfSuppressMapClientErrorsIsSet() + { + // Arrange + var provider = GetProvider(new ApiBehaviorOptions { SuppressMapClientErrors = true }); + + // Act & Assert + Assert.Empty(provider.ActionModelConventions.OfType()); + } + + [Fact] + public void Constructor_DoesNotAddInvalidModelStateFilterConvention_IfSuppressModelStateInvalidFilterIsSet() + { + // Arrange + var provider = GetProvider(new ApiBehaviorOptions { SuppressModelStateInvalidFilter = true }); + + // Act & Assert + Assert.Empty(provider.ActionModelConventions.OfType()); + } + + [Fact] + public void Constructor_DoesNotAddConsumesConstraintForFormFileParameterConvention_IfSuppressConsumesConstraintForFormFileParametersIsSet() + { + // Arrange + var provider = GetProvider(new ApiBehaviorOptions { SuppressConsumesConstraintForFormFileParameters = true }); + + // Act & Assert + Assert.Empty(provider.ActionModelConventions.OfType()); + } + + [Fact] + public void Constructor_SetsSuppressInferBindingSourcesForParametersIsSet() + { + // Arrange + var provider = GetProvider(new ApiBehaviorOptions { SuppressInferBindingSourcesForParameters = true }); + + // Act & Assert + var convention = Assert.Single(provider.ControllerModelConventions.OfType()); + Assert.True(convention.SuppressInferBindingSourcesForParameters); + } + + [Fact] + public void Constructor_DoesNotSpecifyDefaultErrorType_IfSuppressMapClientErrorsIsSet() + { + // Arrange + var provider = GetProvider(new ApiBehaviorOptions { SuppressMapClientErrors = true }); + + // Act & Assert + var convention = Assert.Single(provider.ActionModelConventions.OfType()); + Assert.Equal(typeof(void), convention.DefaultErrorResponseType.Type); + } + + private static ApiBehaviorApplicationModelProvider GetProvider( + ApiBehaviorOptions options = null) + { + options = options ?? new ApiBehaviorOptions + { + InvalidModelStateResponseFactory = _ => null, + }; + var optionsAccessor = Options.Create(options); + + var loggerFactory = NullLoggerFactory.Instance; + return new ApiBehaviorApplicationModelProvider( + optionsAccessor, + new EmptyModelMetadataProvider(), + Mock.Of(), + loggerFactory); + } + + private static ApplicationModelProviderContext GetContext( + Type type, + IModelMetadataProvider modelMetadataProvider = null) + { + var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); + var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }); + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); + var provider = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider); + provider.OnProvidersExecuting(context); + + return context; + } + + private class TestApiController : ControllerBase + { + public IActionResult TestAction(object value) => null; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs new file mode 100644 index 0000000000..643b1fbd60 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs @@ -0,0 +1,221 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.ComponentModel; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApiExplorer; +using Microsoft.AspNetCore.Mvc.Authorization; +using Xunit; + +[assembly: ProducesErrorResponseType(typeof(InvalidEnumArgumentException))] + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class ApiConventionApplicationModelConventionTest + { + [Fact] + public void Apply_DoesNotAddConventionItem_IfActionHasProducesResponseTypeAttribute() + { + // Arrange + var actionModel = GetActionModel(nameof(TestController.Delete)); + actionModel.Filters.Add(new ProducesResponseTypeAttribute(200)); + + var convention = GetConvention(); + + // Act + convention.Apply(actionModel); + + // Assert + Assert.DoesNotContain(typeof(ApiConventionResult), actionModel.Properties.Keys); + } + + [Fact] + public void Apply_DoesNotAddConventionItem_IfActionHasProducesAttribute() + { + // Arrange + var actionModel = GetActionModel(nameof(TestController.Delete)); + actionModel.Filters.Add(new ProducesAttribute(typeof(object))); + + var convention = GetConvention(); + + // Act + convention.Apply(actionModel); + + // Assert + Assert.DoesNotContain(typeof(ApiConventionResult), actionModel.Properties.Keys); + } + + [Fact] + public void Apply_DoesNotAddConventionItem_IfNoConventionMatches() + { + // Arrange + var actionModel = GetActionModel(nameof(TestController.NoMatch)); + var convention = GetConvention(); + + // Act + convention.Apply(actionModel); + + // Assert + Assert.DoesNotContain(typeof(ApiConventionResult), actionModel.Properties.Keys); + } + + [Fact] + public void Apply_AddsConventionItem_IfConventionMatches() + { + // Arrange + var actionModel = GetActionModel(nameof(TestController.Delete)); + var convention = GetConvention(); + + // Act + convention.Apply(actionModel); + + // Assert + var value = actionModel.Properties[typeof(ApiConventionResult)]; + Assert.NotNull(value); + } + + [Fact] + public void Apply_AddsConventionItem_IfActionHasNonConventionBasedFilters() + { + // Arrange + var actionModel = GetActionModel(nameof(TestController.Delete)); + actionModel.Filters.Add(new AuthorizeFilter()); + var convention = GetConvention(); + + // Act + convention.Apply(actionModel); + + // Assert + var value = actionModel.Properties[typeof(ApiConventionResult)]; + Assert.NotNull(value); + } + + [Fact] + public void Apply_UsesDefaultErrorType_IfActionHasNoAttributes() + { + // Arrange + var expected = typeof(InvalidFilterCriteriaException); + var controller = new ControllerModel(typeof(object).GetTypeInfo(), Array.Empty()); + var action = new ActionModel(typeof(object).GetMethods()[0], Array.Empty()) + { + Controller = controller, + }; + var convention = GetConvention(expected); + + // Act + convention.Apply(action); + + // Assert + var attribute = GetProperty(action); + Assert.Equal(expected, attribute.Type); + } + + [Fact] + public void Apply_UsesValueFromProducesErrorResponseTypeAttribute_SpecifiedOnControllerAsssembly() + { + // Arrange + var expected = typeof(InvalidEnumArgumentException); + var action = GetActionModel(nameof(TestController.Delete)); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + var attribute = GetProperty(action); + Assert.Equal(expected, attribute.Type); + } + + [Fact] + public void Apply_UsesValueFromProducesErrorResponseTypeAttribute_SpecifiedOnController() + { + // Arrange + var expected = typeof(InvalidTimeZoneException); + var action = GetActionModel( + nameof(TestController.Delete), + controllerAttributes: new[] { new ProducesErrorResponseTypeAttribute(expected) }); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + var attribute = GetProperty(action); + Assert.Equal(expected, attribute.Type); + } + + [Fact] + public void Apply_UsesValueFromProducesErrorResponseTypeAttribute_SpecifiedOnAction() + { + // Arrange + var expected = typeof(InvalidTimeZoneException); + var action = GetActionModel( + nameof(TestController.Delete), + actionAttributes: new[] { new ProducesErrorResponseTypeAttribute(expected) }, + controllerAttributes: new[] { new ProducesErrorResponseTypeAttribute(typeof(Guid)) }); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + var attribute = GetProperty(action); + Assert.Equal(expected, attribute.Type); + } + + [Fact] + public void Apply_AllowsVoidsErrorType() + { + // Arrange + var expected = typeof(void); + var action = GetActionModel(nameof(TestController.Delete), new[] { new ProducesErrorResponseTypeAttribute(expected) }); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + var attribute = GetProperty(action); + Assert.Equal(expected, attribute.Type); + } + + private ApiConventionApplicationModelConvention GetConvention(Type errorType = null) + { + errorType = errorType ?? typeof(ProblemDetails); + return new ApiConventionApplicationModelConvention(new ProducesErrorResponseTypeAttribute(errorType)); + } + + private static TValue GetProperty(ActionModel action) + { + return Assert.IsType(action.Properties[typeof(TValue)]); + } + + private static ActionModel GetActionModel( + string actionName, + object[] actionAttributes = null, + object[] controllerAttributes = null) + { + actionAttributes = actionAttributes ?? Array.Empty(); + controllerAttributes = controllerAttributes ?? new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; + + var controllerModel = new ControllerModel(typeof(TestController).GetTypeInfo(), controllerAttributes); + var actionModel = new ActionModel(typeof(TestController).GetMethod(actionName), actionAttributes) + { + Controller = controllerModel, + }; + + controllerModel.Actions.Add(actionModel); + + return actionModel; + } + + private class TestController + { + public IActionResult NoMatch() => null; + + public IActionResult Delete(int id) => null; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs new file mode 100644 index 0000000000..3928909e04 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs @@ -0,0 +1,63 @@ +// 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.Reflection; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class ApiVisibilityConventionTest + { + [Fact] + public void Apply_SetsApiExplorerVisibility() + { + // Arrange + var action = GetActionModel(); + var convention = new ApiVisibilityConvention(); + + // Act + convention.Apply(action); + + // Assert + Assert.True(action.ApiExplorer.IsVisible); + } + + [Fact] + public void Apply_DoesNotSetApiExplorerVisibility_IfAlreadySpecifiedOnAction() + { + // Arrange + var action = GetActionModel(); + action.ApiExplorer.IsVisible = false; + var convention = new ApiVisibilityConvention(); + + // Act + convention.Apply(action); + + // Assert + Assert.False(action.ApiExplorer.IsVisible); + } + + [Fact] + public void Apply_DoesNotSetApiExplorerVisibility_IfAlreadySpecifiedOnController() + { + // Arrange + var action = GetActionModel(); + action.Controller.ApiExplorer.IsVisible = false; + var convention = new ApiVisibilityConvention(); + + // Act + convention.Apply(action); + + // Assert + Assert.Null(action.ApiExplorer.IsVisible); + } + + private static ActionModel GetActionModel() + { + return new ActionModel(typeof(object).GetMethods()[0], new object[0]) + { + Controller = new ControllerModel(typeof(object).GetTypeInfo(), new object[0]), + }; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs new file mode 100644 index 0000000000..4fc09baf2b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs @@ -0,0 +1,38 @@ +// 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.Linq; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class ClientErrorResultFilterConventionTest + { + [Fact] + public void Apply_AddsFilter() + { + // Arrange + var action = GetActionModel(); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + Assert.Single(action.Filters.OfType()); + } + + private ClientErrorResultFilterConvention GetConvention() + { + return new ClientErrorResultFilterConvention(); + } + + private static ActionModel GetActionModel() + { + var action = new ActionModel(typeof(object).GetMethods()[0], new object[0]); + + return action; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs new file mode 100644 index 0000000000..9f7d07402a --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs @@ -0,0 +1,104 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class ConsumesConstraintForFormFileParameterConventionTest + { + [Fact] + public void AddMultipartFormDataConsumesAttribute_NoOpsIfConsumesConstraintIsAlreadyPresent() + { + // Arrange + var actionName = nameof(TestController.ActionWithConsumesAttribute); + var action = GetActionModel(typeof(TestController), actionName); + var convention = GetConvention(); + + // Act + convention.AddMultipartFormDataConsumesAttribute(action); + + // Assert + var attribute = Assert.Single(action.Filters); + var consumesAttribute = Assert.IsType(attribute); + Assert.Equal("application/json", Assert.Single(consumesAttribute.ContentTypes)); + } + + [Fact] + public void AddMultipartFormDataConsumesAttribute_AddsConsumesAttribute_WhenActionHasFromFormFileParameter() + { + // Arrange + var actionName = nameof(TestController.FormFileParameter); + var action = GetActionModel(typeof(TestController), actionName); + action.Parameters[0].BindingInfo = new BindingInfo + { + BindingSource = BindingSource.FormFile, + }; + var convention = GetConvention(); + + // Act + convention.AddMultipartFormDataConsumesAttribute(action); + + // Assert + var attribute = Assert.Single(action.Filters); + var consumesAttribute = Assert.IsType(attribute); + Assert.Equal("multipart/form-data", Assert.Single(consumesAttribute.ContentTypes)); + } + + private ConsumesConstraintForFormFileParameterConvention GetConvention() + { + return new ConsumesConstraintForFormFileParameterConvention(); + } + + private static ApplicationModelProviderContext GetContext( + Type type, + IModelMetadataProvider modelMetadataProvider = null) + { + var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); + var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }); + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); + var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider); + convention.OnProvidersExecuting(context); + + return context; + } + + private static ControllerModel GetControllerModel(Type controllerType) + { + var context = GetContext(controllerType); + return Assert.Single(context.Result.Controllers); + } + + private static ActionModel GetActionModel(Type controllerType, string actionName) + { + var context = GetContext(controllerType); + var controller = Assert.Single(context.Result.Controllers); + return Assert.Single(controller.Actions, m => m.ActionName == actionName); + } + + private class TestController + { + [HttpPost("form-file")] + public IActionResult FormFileParameter(IFormFile formFile) => null; + + [HttpPost("form-file-collection")] + public IActionResult FormFileCollectionParameter(IFormFileCollection formFiles) => null; + + [HttpPost("form-file-sequence")] + public IActionResult FormFileSequenceParameter(IFormFile[] formFiles) => null; + + [HttpPost] + public IActionResult FromFormParameter([FromForm] string parameter) => null; + + [HttpPost] + [Consumes("application/json")] + public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs new file mode 100644 index 0000000000..b836f3f70c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs @@ -0,0 +1,986 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class InferParameterBindingInfoConventionTest + { + [Fact] + public void Apply_DoesNotInferBindingSourceForParametersWithBindingInfo() + { + // Arrange + var actionName = nameof(ParameterWithBindingInfo.Action); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterWithBindingInfo), actionName); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameterModel = Assert.Single(action.Parameters); + Assert.NotNull(parameterModel.BindingInfo); + Assert.Same(BindingSource.Custom, parameterModel.BindingInfo.BindingSource); + } + + [Fact] + public void InferParameterBindingSources_Throws_IfMultipleParametersAreInferredAsBodyBound() + { + // Arrange + var actionName = nameof(MultipleFromBodyController.MultipleInferred); + var expected = +$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " + +"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + +Environment.NewLine + "TestModel a" + +Environment.NewLine + "Car b"; + + var convention = GetConvention(); + var action = GetActionModel(typeof(MultipleFromBodyController), actionName); + + // Act & Assert + var ex = Assert.Throws(() => convention.InferParameterBindingSources(action)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void InferParameterBindingSources_Throws_IfMultipleParametersAreInferredOrSpecifiedAsBodyBound() + { + // Arrange + var actionName = nameof(MultipleFromBodyController.InferredAndSpecified); + var expected = +$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " + +"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + +Environment.NewLine + "TestModel a" + +Environment.NewLine + "int b"; + + var convention = GetConvention(); + var action = GetActionModel(typeof(MultipleFromBodyController), actionName); + + // Act & Assert + var ex = Assert.Throws(() => convention.InferParameterBindingSources(action)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void InferParameterBindingSources_Throws_IfMultipleParametersAreFromBody() + { + // Arrange + var actionName = nameof(MultipleFromBodyController.MultipleSpecified); + var expected = +$@"Action '{typeof(MultipleFromBodyController).FullName}.{actionName} ({typeof(MultipleFromBodyController).Assembly.GetName().Name})' " + +"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + +Environment.NewLine + "decimal a" + +Environment.NewLine + "int b"; + + var convention = GetConvention(); + var action = GetActionModel(typeof(MultipleFromBodyController), actionName); + + // Act & Assert + var ex = Assert.Throws(() => convention.InferParameterBindingSources(action)); + Assert.Equal(expected, ex.Message); + } + + [Fact] + public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder_AndExplicitName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderAttributeWithExplicitModelName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("top", bindingInfo.BinderModelName); + } + + [Fact] + public void Apply_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderType); + var convention = GetConvention(); + var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType_AndExplicitModelName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ModelBinderOnParameterController.ModelBinderTypeWithExplicitModelName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ModelBinderOnParameterController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); + Assert.Equal("foo", bindingInfo.BinderModelName); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsSimpleToken() + { + // Arrange + var actionName = nameof(ParameterBindingController.SimpleRouteToken); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsOptionalToken() + { + // Arrange + var actionName = nameof(ParameterBindingController.OptionalRouteToken); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsConstrainedToken() + { + // Arrange + var actionName = nameof(ParameterBindingController.ConstrainedRouteToken); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInAbsoluteRoute() + { + // Arrange + var actionName = nameof(ParameterBindingController.AbsoluteRoute); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MulitpleRoutes() + { + // Arrange + var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoute() + { + // Arrange + var actionName = nameof(ParameterBindingController.ParameterNotInAllRoutes); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute() + { + // Arrange + var actionName = nameof(ParameterInController.ActionWithoutRoute); + var parameter = GetParameterModel(typeof(ParameterInController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute_AndActionHasRoute() + { + // Arrange + var actionName = nameof(ParameterInController.ActionWithRoute); + var parameter = GetParameterModel(typeof(ParameterInController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAllActionRoutes() + { + // Arrange + var actionName = nameof(ParameterInController.MultipleRoute); + var parameter = GetParameterModel(typeof(ParameterInController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_DoesNotReturnPath_IfActionRouteOverridesControllerRoute() + { + // Arrange + var actionName = nameof(ParameterInController.AbsoluteRoute); + var parameter = GetParameterModel(typeof(ParameterInController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Query, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterPresentInNonOverriddenControllerRoute() + { + // Arrange + var actionName = nameof(ParameterInController.MultipleRouteWithOverride); + var parameter = GetParameterModel(typeof(ParameterInController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInRoute_OnControllersWithoutSelectors() + { + // Arrange + var actionName = nameof(ParameterBindingNoRoutesOnController.SimpleRoute); + var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInAllRoutes_OnControllersWithoutSelectors() + { + // Arrange + var actionName = nameof(ParameterBindingNoRoutesOnController.ParameterInMultipleRoutes); + var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Path, result); + } + + [Fact] + public void InferBindingSourceForParameter_DoesNotReturnPath_IfNeitherActionNorControllerHasTemplate() + { + // Arrange + var actionName = nameof(ParameterBindingNoRoutesOnController.NoRouteTemplate); + var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Query, result); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsBodyForComplexTypes() + { + // Arrange + var actionName = nameof(ParameterBindingController.ComplexTypeModel); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Body, result); + } + + [Fact] + public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypesWithCancellationToken() + { + // Arrange + var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); + + // Use the default set of ModelMetadataProviders so we get metadata details for CancellationToken. + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); + var controllerModel = Assert.Single(context.Result.Controllers); + var actionModel = Assert.Single(controllerModel.Actions, m => m.ActionName == actionName); + + var convention = GetConvention(); + + // Act + convention.InferParameterBindingSources(actionModel); + + // Assert + var model = GetParameterModel(actionModel); + Assert.Same(BindingSource.Body, model.BindingInfo.BindingSource); + + var cancellationToken = GetParameterModel(actionModel); + Assert.Same(BindingSource.Special, cancellationToken.BindingInfo.BindingSource); + } + + [Fact] + public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes() + { + // Arrange + var actionName = nameof(ParameterBindingController.SimpleTypeModel); + var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); + var convention = GetConvention(); + + // Act + var result = convention.InferBindingSourceForParameter(parameter); + + // Assert + Assert.Same(BindingSource.Query, result); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQuery); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameter_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryWithCustomName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("top", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnComplexType); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnComplexTypeWithCustomName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("gps", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnCollectionType); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromQueryOnArrayType() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayType); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromQueryOnArrayTypeWithCustomName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Equal("ids", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRoute); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameter_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteWithCustomName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Equal("top", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithDefaultName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteOnComplexType); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + } + + [Fact] + public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithCustomName() + { + // Arrange + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.FromRouteOnComplexTypeWithCustomName); + var convention = GetConvention(); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Path, bindingInfo.BindingSource); + Assert.Equal("gps", bindingInfo.BinderModelName); + } + + [Fact] + public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndPropertyFilterProvider() + { + // Arrange + var expectedPredicate = CustomRequestPredicateAndPropertyFilterProviderAttribute.RequestPredicateStatic; + var expectedPropertyFilter = CustomRequestPredicateAndPropertyFilterProviderAttribute.PropertyFilterStatic; + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var actionName = nameof(ParameterBindingController.ParameterWithRequestPredicateProvider); + var action = GetActionModel(typeof(ParameterBindingController), actionName, modelMetadataProvider); + var convention = GetConvention(); + + // Act + convention.Apply(action.Controller); + + // Assert + var parameter = Assert.Single(action.Parameters); + + var bindingInfo = parameter.BindingInfo; + Assert.NotNull(bindingInfo); + Assert.Same(BindingSource.Query, bindingInfo.BindingSource); + Assert.Same(expectedPredicate, bindingInfo.RequestPredicate); + Assert.Same(expectedPropertyFilter, bindingInfo.PropertyFilterProvider.PropertyFilter); + Assert.Null(bindingInfo.BinderModelName); + } + + [Fact] + public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() + { + // Arrange + var controller = GetControllerModel(typeof(ControllerWithBoundProperty)); + var convention = GetConvention(); + + // Act + convention.InferBoundPropertyModelPrefixes(controller); + + // Assert + var property = Assert.Single(controller.ControllerProperties); + Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); + } + + [Fact] + public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider() + { + // Arrange + var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty)); + var convention = GetConvention(); + + // Act + convention.InferBoundPropertyModelPrefixes(controller); + + // Assert + var property = Assert.Single(controller.ControllerProperties); + Assert.Null(property.BindingInfo.BinderModelName); + } + + [Fact] + public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() + { + // Arrange + var action = GetActionModel(typeof(ControllerWithBoundProperty), nameof(ControllerWithBoundProperty.SomeAction)); + var convention = GetConvention(); + + // Act + convention.InferParameterModelPrefixes(action); + + // Assert + var parameter = Assert.Single(action.Parameters); + Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName); + } + + private static InferParameterBindingInfoConvention GetConvention( + IModelMetadataProvider modelMetadataProvider = null) + { + var loggerFactory = NullLoggerFactory.Instance; + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); + return new InferParameterBindingInfoConvention(modelMetadataProvider); + } + + private static ApplicationModelProviderContext GetContext( + Type type, + IModelMetadataProvider modelMetadataProvider = null) + { + var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); + var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }); + modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); + var convention = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider); + convention.OnProvidersExecuting(context); + + return context; + } + + private static ControllerModel GetControllerModel( + Type controllerType, + IModelMetadataProvider modelMetadataProvider = null) + { + var context = GetContext(controllerType, modelMetadataProvider); + return Assert.Single(context.Result.Controllers); + } + + private static ActionModel GetActionModel( + Type controllerType, + string actionName, + IModelMetadataProvider modelMetadataProvider = null) + { + var context = GetContext(controllerType, modelMetadataProvider); + var controller = Assert.Single(context.Result.Controllers); + return Assert.Single(controller.Actions, m => m.ActionName == actionName); + } + + private static ParameterModel GetParameterModel(Type controllerType, string actionName) + { + var action = GetActionModel(controllerType, actionName); + return Assert.Single(action.Parameters); + } + + private static ParameterModel GetParameterModel(ActionModel action) + { + return Assert.Single(action.Parameters.Where(x => typeof(T).IsAssignableFrom(x.ParameterType))); + } + + [ApiController] + [Route("[controller]/[action]")] + private class ParameterBindingController + { + [HttpGet("{parameter}")] + public IActionResult ActionWithBoundParameter([FromBody] object parameter) => null; + + [HttpGet("{id}")] + public IActionResult SimpleRouteToken(int id) => null; + + [HttpPost("optional/{id?}")] + public IActionResult OptionalRouteToken(int id) => null; + + [HttpDelete("delete-by-status/{status:int?}")] + public IActionResult ConstrainedRouteToken(object status) => null; + + [HttpPut("/absolute-route/{status:int}")] + public IActionResult AbsoluteRoute(object status) => null; + + [HttpPost("multiple/{id}")] + [HttpPut("multiple/{id}")] + public IActionResult ParameterInMultipleRoutes(int id) => null; + + [HttpPatch("patchroute")] + [HttpPost("multiple/{id}")] + [HttpPut("multiple/{id}")] + public IActionResult ParameterNotInAllRoutes(int id) => null; + + [HttpPut("put-action/{id}")] + public IActionResult ComplexTypeModel(TestModel model) => null; + + [HttpPut("put-action/{id}")] + public IActionResult SimpleTypeModel(ConvertibleFromString model) => null; + + [HttpPost("form-file")] + public IActionResult FormFileParameter(IFormFile formFile) => null; + + [HttpPost("form-file-collection")] + public IActionResult FormFileCollectionParameter(IFormFileCollection formFiles) => null; + + [HttpPost("form-file-sequence")] + public IActionResult FormFileSequenceParameter(IFormFile[] formFiles) => null; + + [HttpPost] + public IActionResult FromFormParameter([FromForm] string parameter) => null; + + [HttpPost] + [Consumes("application/json")] + public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null; + + [HttpPut("cancellation")] + public IActionResult ComplexTypeModelWithCancellationToken(TestModel model, CancellationToken cancellationToken) => null; + + [HttpGet("parameter-with-model-binder-attribute")] + public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromquery")] + public IActionResult FromQuery([FromQuery] int value) => null; + + [HttpGet("parameter-with-fromquery-and-customname")] + public IActionResult FromQueryWithCustomName([FromQuery(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromquery-on-complextype")] + public IActionResult FromQueryOnComplexType([FromQuery] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromquery-on-complextype-and-customname")] + public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromquery-on-collection-type")] + public IActionResult FromQueryOnCollectionType([FromQuery] ICollection value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type")] + public IActionResult FromQueryOnArrayType([FromQuery] int[] value) => null; + + [HttpGet("parameter-with-fromquery-on-array-type-customname")] + public IActionResult FromQueryOnArrayTypeWithCustomName([FromQuery(Name = "ids")] int[] value) => null; + + [HttpGet("parameter-with-fromroute")] + public IActionResult FromRoute([FromRoute] int value) => null; + + [HttpGet("parameter-with-fromroute-and-customname")] + public IActionResult FromRouteWithCustomName([FromRoute(Name = "top")] int value) => null; + + [HttpGet("parameter-with-fromroute-on-complextype")] + public IActionResult FromRouteOnComplexType([FromRoute] GpsCoordinates gpsCoordinates) => null; + + [HttpGet("parameter-with-fromroute-on-complextype-and-customname")] + public IActionResult FromRouteOnComplexTypeWithCustomName([FromRoute(Name = "gps")] GpsCoordinates gpsCoordinates) => null; + + [HttpGet] + public IActionResult ParameterWithRequestPredicateProvider([CustomRequestPredicateAndPropertyFilterProvider] int value) => null; + } + + [ApiController] + [Route("[controller]/[action]")] + private class ModelBinderOnParameterController + { + [HttpGet] + public IActionResult ModelBinderAttributeWithExplicitModelName([ModelBinder(Name = "top")] int value) => null; + + [HttpGet] + public IActionResult ModelBinderType([ModelBinder(typeof(TestModelBinder))] string name) => null; + + [HttpGet] + public IActionResult ModelBinderTypeWithExplicitModelName([ModelBinder(typeof(TestModelBinder), Name = "foo")] string name) => null; + } + + [ApiController] + [Route("/route1/[controller]/[action]/{id}")] + [Route("/route2/[controller]/[action]/{id?}")] + private class ParameterInController + { + [HttpGet] + public IActionResult ActionWithoutRoute(int id) => null; + + [HttpGet("stuff/{status}")] + public IActionResult ActionWithRoute(int id) => null; + + [HttpGet("/absolute-route")] + public IActionResult AbsoluteRoute(int id) => null; + + [HttpPut] + [HttpPost("stuff/{status}")] + public IActionResult MultipleRoute(int id) => null; + + [HttpPut] + [HttpPost("~/stuff/{status}")] + public IActionResult MultipleRouteWithOverride(int id) => null; + } + + [ApiController] + private class ParameterBindingNoRoutesOnController + { + [HttpGet("{parameter}")] + public IActionResult SimpleRoute(int parameter) => null; + + [HttpGet] + public IActionResult NoRouteTemplate(int id) => null; + + [HttpPost("multiple/{id}")] + [HttpPut("multiple/{id}")] + public IActionResult ParameterInMultipleRoutes(int id) => null; + } + + + private class TestModel { } + + [TypeConverter(typeof(ConvertibleFromStringConverter))] + private class ConvertibleFromString { } + + private class ConvertibleFromStringConverter : TypeConverter + { + public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) + => sourceType == typeof(string); + } + + private class CustomRequestPredicateAndPropertyFilterProviderAttribute : Attribute, IRequestPredicateProvider, IPropertyFilterProvider + { + public static Func RequestPredicateStatic => (c) => true; + public static Func PropertyFilterStatic => (c) => true; + + public Func RequestPredicate => RequestPredicateStatic; + + public Func PropertyFilter => PropertyFilterStatic; + } + + private class GpsCoordinates + { + public long Latitude { get; set; } + public long Longitude { get; set; } + } + + private class TestModelBinder : IModelBinder + { + public Task BindModelAsync(ModelBindingContext bindingContext) + { + throw new NotImplementedException(); + } + } + + private class ControllerWithBoundProperty + { + [FromQuery] + public TestModel TestProperty { get; set; } + + public IActionResult SomeAction([FromQuery] TestModel test) => null; + } + + private class ControllerWithBoundCollectionProperty + { + [FromQuery] + public List TestProperty { get; set; } + + public IActionResult SomeAction([FromQuery] List test) => null; + } + + private class Car { } + + private class MultipleFromBodyController + { + public IActionResult MultipleInferred(TestModel a, Car b) => null; + + public IActionResult InferredAndSpecified(TestModel a, [FromBody] int b) => null; + + public IActionResult MultipleSpecified([FromBody] decimal a, [FromBody] int b) => null; + } + + private class ParameterWithBindingInfo + { + [HttpGet("test")] + public IActionResult Action([ModelBinder(typeof(object))] Car car) => null; + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs new file mode 100644 index 0000000000..080905cb4e --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs @@ -0,0 +1,39 @@ +// 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.Linq; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class InvalidModelStateFilterConventionTest + { + [Fact] + public void Apply_AddsFilter() + { + // Arrange + var action = GetActionModel(); + var convention = GetConvention(); + + // Act + convention.Apply(action); + + // Assert + Assert.Single(action.Filters.OfType()); + } + + + private static ActionModel GetActionModel() + { + var action = new ActionModel(typeof(object).GetMethods()[0], new object[0]); + + return action; + } + + private InvalidModelStateFilterConvention GetConvention() + { + return new InvalidModelStateFilterConvention(); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/AttributeRouteModelTests.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ControllerModelTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/ParameterModelTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/PropertyModelTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/PropertyModelTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs deleted file mode 100644 index 23241174ca..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorApplicationModelProviderTest.cs +++ /dev/null @@ -1,1591 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.ApiExplorer; -using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Extensions.Logging.Abstractions; -using Microsoft.Extensions.Options; -using Moq; -using Xunit; - -[assembly: Microsoft.AspNetCore.Mvc.ProducesErrorResponseType(typeof(InvalidEnumArgumentException))] - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public class ApiBehaviorApplicationModelProviderTest - { - [Fact] - public void OnProvidersExecuting_AddsModelStateInvalidFilter_IfTypeIsAnnotatedWithAttribute() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); - Assert.Single(actionModel.Filters.OfType()); - } - - [Fact] - public void OnProvidersExecuting_DoesNotAddModelStateInvalidFilterToController_IfFeatureIsDisabledViaOptions() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - var options = new ApiBehaviorOptions - { - SuppressModelStateInvalidFilter = true, - }; - - var provider = GetProvider(options); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); - Assert.Empty(actionModel.Filters.OfType()); - } - - [Fact] - public void OnProvidersExecuting_AddsModelStateInvalidFilter_IfActionIsAnnotatedWithAttribute() - { - // Arrange - var context = GetContext(typeof(SimpleController)); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - Assert.Collection( - Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName), - action => - { - Assert.Single(action.Filters.OfType()); - }, - action => - { - Assert.Empty(action.Filters.OfType()); - }); - } - - [Fact] - public void OnProvidersExecuting_SkipsAddingFilterToActionIfFeatureIsDisabledUsingOptions() - { - // Arrange - var context = GetContext(typeof(SimpleController)); - var options = new ApiBehaviorOptions - { - SuppressModelStateInvalidFilter = true, - }; - - var provider = GetProvider(options); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - Assert.Collection( - Assert.Single(context.Result.Controllers).Actions.OrderBy(a => a.ActionName), - action => - { - Assert.Empty(action.Filters.OfType()); - }, - action => - { - Assert.Empty(action.Filters.OfType()); - }); - } - - [Fact] - public void OnProvidersExecuting_MakesControllerVisibleInApiExplorer_IfItIsAnnotatedWithAttribute() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - var options = new ApiBehaviorOptions - { - SuppressModelStateInvalidFilter = true, - }; - - var provider = GetProvider(options); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - Assert.True(controller.ApiExplorer.IsVisible); - } - - [Fact] - public void OnProvidersExecuting_DoesNotModifyVisibilityInApiExplorer_IfValueIsAlreadySet() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - context.Result.Controllers[0].ApiExplorer.IsVisible = false; - var options = new ApiBehaviorOptions - { - SuppressModelStateInvalidFilter = true, - }; - - var provider = GetProvider(options); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - Assert.False(controller.ApiExplorer.IsVisible); - } - - [Fact] - public void OnProvidersExecuting_ThrowsIfControllerWithAttribute_HasActionsWithoutAttributeRouting() - { - // Arrange - var actionName = $"{typeof(ActionsWithoutAttributeRouting).FullName}.{nameof(ActionsWithoutAttributeRouting.Index)} ({typeof(ActionsWithoutAttributeRouting).Assembly.GetName().Name})"; - var expected = $"Action '{actionName}' does not have an attribute route. Action methods on controllers annotated with ApiControllerAttribute must be attribute routed."; - var context = GetContext(typeof(ActionsWithoutAttributeRouting)); - var provider = GetProvider(); - - // Act & Assert - var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsSimpleToken() - { - // Arrange - var actionName = nameof(ParameterBindingController.SimpleRouteToken); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsOptionalToken() - { - // Arrange - var actionName = nameof(ParameterBindingController.OptionalRouteToken); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInRouteAsConstrainedToken() - { - // Arrange - var actionName = nameof(ParameterBindingController.ConstrainedRouteToken); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterNameExistsInAbsoluteRoute() - { - // Arrange - var actionName = nameof(ParameterBindingController.AbsoluteRoute); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoutes_MultipleRoutes() - { - // Arrange - var actionName = nameof(ParameterBindingController.ParameterInMultipleRoutes); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAnyRoute() - { - // Arrange - var actionName = nameof(ParameterBindingController.ParameterNotInAllRoutes); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute() - { - // Arrange - var actionName = nameof(ParameterInController.ActionWithoutRoute); - var parameter = GetParameterModel(typeof(ParameterInController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInControllerRoute_AndActionHasRoute() - { - // Arrange - var actionName = nameof(ParameterInController.ActionWithRoute); - var parameter = GetParameterModel(typeof(ParameterInController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterAppearsInAllActionRoutes() - { - // Arrange - var actionName = nameof(ParameterInController.MultipleRoute); - var parameter = GetParameterModel(typeof(ParameterInController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_DoesNotReturnPath_IfActionRouteOverridesControllerRoute() - { - // Arrange - var actionName = nameof(ParameterInController.AbsoluteRoute); - var parameter = GetParameterModel(typeof(ParameterInController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Query, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterPresentInNonOverriddenControllerRoute() - { - // Arrange - var actionName = nameof(ParameterInController.MultipleRouteWithOverride); - var parameter = GetParameterModel(typeof(ParameterInController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInRoute_OnControllersWithoutSelectors() - { - // Arrange - var actionName = nameof(ParameterBindingNoRoutesOnController.SimpleRoute); - var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsPath_IfParameterExistsInAllRoutes_OnControllersWithoutSelectors() - { - // Arrange - var actionName = nameof(ParameterBindingNoRoutesOnController.ParameterInMultipleRoutes); - var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Path, result); - } - - [Fact] - public void InferBindingSourceForParameter_DoesNotReturnPath_IfNeitherActionNorControllerHasTemplate() - { - // Arrange - var actionName = nameof(ParameterBindingNoRoutesOnController.NoRouteTemplate); - var parameter = GetParameterModel(typeof(ParameterBindingNoRoutesOnController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Query, result); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsBodyForComplexTypes() - { - // Arrange - var actionName = nameof(ParameterBindingController.ComplexTypeModel); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Body, result); - } - - [Fact] - public void OnProvidersExecuting_DoesNotInferBindingSourceForParametersWithBindingInfo() - { - // Arrange - var actionName = nameof(ParameterWithBindingInfo.Action); - var provider = GetProvider(); - var context = GetContext(typeof(ParameterWithBindingInfo)); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controllerModel = Assert.Single(context.Result.Controllers); - var actionModel = Assert.Single(controllerModel.Actions, a => a.ActionName == actionName); - var parameterModel = Assert.Single(actionModel.Parameters); - Assert.NotNull(parameterModel.BindingInfo); - Assert.Same(BindingSource.Custom, parameterModel.BindingInfo.BindingSource); - } - - [Fact] - public void OnProvidersExecuting_Throws_IfMultipleParametersAreInferredAsBodyBound() - { - // Arrange - var expected = -$@"Action '{typeof(ControllerWithMultipleInferredFromBodyParameters).FullName}.{nameof(ControllerWithMultipleInferredFromBodyParameters.Action)} ({typeof(ControllerWithMultipleInferredFromBodyParameters).Assembly.GetName().Name})' " + -"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + -Environment.NewLine + "TestModel a" + -Environment.NewLine + "Car b"; - - var context = GetContext(typeof(ControllerWithMultipleInferredFromBodyParameters)); - var provider = GetProvider(); - - // Act & Assert - var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void OnProvidersExecuting_Throws_IfMultipleParametersAreInferredOrSpecifiedAsBodyBound() - { - // Arrange - var expected = -$@"Action '{typeof(ControllerWithMultipleInferredOrSpecifiedFromBodyParameters).FullName}.{nameof(ControllerWithMultipleInferredOrSpecifiedFromBodyParameters.Action)} ({typeof(ControllerWithMultipleInferredOrSpecifiedFromBodyParameters).Assembly.GetName().Name})' " + -"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + -Environment.NewLine + "TestModel a" + -Environment.NewLine + "int b"; - - var context = GetContext(typeof(ControllerWithMultipleInferredOrSpecifiedFromBodyParameters)); - var provider = GetProvider(); - - // Act & Assert - var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void OnProvidersExecuting_Throws_IfMultipleParametersAreFromBody() - { - // Arrange - var expected = -$@"Action '{typeof(ControllerWithMultipleFromBodyParameters).FullName}.{nameof(ControllerWithMultipleFromBodyParameters.Action)} ({typeof(ControllerWithMultipleFromBodyParameters).Assembly.GetName().Name})' " + -"has more than one parameter that was specified or inferred as bound from request body. Only one parameter per action may be bound from body. Inspect the following parameters, and use 'FromQueryAttribute' to specify bound from query, 'FromRouteAttribute' to specify bound from route, and 'FromBodyAttribute' for parameters to be bound from body:" + -Environment.NewLine + "decimal a" + -Environment.NewLine + "int b"; - - var context = GetContext(typeof(ControllerWithMultipleFromBodyParameters)); - var provider = GetProvider(); - - // Act & Assert - var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); - Assert.Equal(expected, ex.Message); - } - - [Fact] - public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinder_AndExplicitName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ModelBinderOnParameterController.ModelBinderAttributeWithExplicitModelName); - var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Equal("top", bindingInfo.BinderModelName); - } - - [Fact] - public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ModelBinderOnParameterController.ModelBinderType); - var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void OnProvidersExecuting_PreservesBindingInfo_WhenInferringFor_ParameterWithModelBinderType_AndExplicitModelName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ModelBinderOnParameterController.ModelBinderTypeWithExplicitModelName); - var context = GetContext(typeof(ModelBinderOnParameterController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Custom, bindingInfo.BindingSource); - Assert.Equal("foo", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryParameter_WithDefaultName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQuery); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryParameter_WithCustomName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryWithCustomName); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Equal("top", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithDefaultName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryOnComplexType); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Equal(string.Empty, bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryParameterOnComplexType_WithCustomName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryOnComplexTypeWithCustomName); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Equal("gps", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryParameterOnCollectionType() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryOnCollectionType); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromQueryOnArrayType() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryOnArrayType); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_FromQueryOnArrayTypeWithCustomName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromQueryOnArrayTypeWithCustomName); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Equal("ids", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromRouteParameter_WithDefaultName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromRoute); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Path, bindingInfo.BindingSource); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromRouteParameter_WithCustomName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromRouteWithCustomName); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Path, bindingInfo.BindingSource); - Assert.Equal("top", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithDefaultName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromRouteOnComplexType); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Path, bindingInfo.BindingSource); - Assert.Equal(string.Empty, bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForFromRouteParameterOnComplexType_WithCustomName() - { - // Arrange - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.FromRouteOnComplexTypeWithCustomName); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Path, bindingInfo.BindingSource); - Assert.Equal("gps", bindingInfo.BinderModelName); - } - - [Fact] - public void PreservesBindingSourceInference_ForParameterWithRequestPredicateAndPropertyFilterProvider() - { - // Arrange - var expectedPredicate = CustomRequestPredicateAndPropertyFilterProviderAttribute.RequestPredicateStatic; - var expectedPropertyFilter = CustomRequestPredicateAndPropertyFilterProviderAttribute.PropertyFilterStatic; - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var actionName = nameof(ParameterBindingController.ParameterWithRequestPredicateProvider); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var controller = Assert.Single(context.Result.Controllers); - var action = Assert.Single(controller.Actions, a => a.ActionName == actionName); - var parameter = Assert.Single(action.Parameters); - - var bindingInfo = parameter.BindingInfo; - Assert.NotNull(bindingInfo); - Assert.Same(BindingSource.Query, bindingInfo.BindingSource); - Assert.Same(expectedPredicate, bindingInfo.RequestPredicate); - Assert.Same(expectedPropertyFilter, bindingInfo.PropertyFilterProvider.PropertyFilter); - Assert.Null(bindingInfo.BinderModelName); - } - - [Fact] - public void InferParameterBindingSources_SetsCorrectBindingSourceForComplexTypesWithCancellationToken() - { - // Arrange - var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); - - // Use the default set of ModelMetadataProviders so we get metadata details for CancellationToken. - var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); - var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); - var controllerModel = Assert.Single(context.Result.Controllers); - var actionModel = Assert.Single(controllerModel.Actions, m => m.ActionName == actionName); - - var provider = GetProvider(); - - // Act - provider.InferParameterBindingSources(actionModel); - - // Assert - var model = GetParameterModel(actionModel); - Assert.Same(BindingSource.Body, model.BindingInfo.BindingSource); - - var cancellationToken = GetParameterModel(actionModel); - Assert.Same(BindingSource.Special, cancellationToken.BindingInfo.BindingSource); - } - - [Fact] - public void InferBindingSourceForParameter_ReturnsBodyForSimpleTypes() - { - // Arrange - var actionName = nameof(ParameterBindingController.SimpleTypeModel); - var parameter = GetParameterModel(typeof(ParameterBindingController), actionName); - var provider = GetProvider(); - - // Act - var result = provider.InferBindingSourceForParameter(parameter); - - // Assert - Assert.Same(BindingSource.Query, result); - } - - [Fact] - public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() - { - // Arrange - var controller = GetControllerModel(typeof(ControllerWithBoundProperty)); - - var provider = GetProvider(); - - // Act - provider.InferBoundPropertyModelPrefixes(controller); - - // Assert - var property = Assert.Single(controller.ControllerProperties); - Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); - } - - [Fact] - public void InferBoundPropertyModelPrefixes_SetsModelPrefix_ForCollectionTypeFromValueProvider() - { - // Arrange - var controller = GetControllerModel(typeof(ControllerWithBoundCollectionProperty)); - - var provider = GetProvider(); - - // Act - provider.InferBoundPropertyModelPrefixes(controller); - - // Assert - var property = Assert.Single(controller.ControllerProperties); - Assert.Null(property.BindingInfo.BinderModelName); - } - - [Fact] - public void InferParameterModelPrefixes_SetsModelPrefix_ForComplexTypeFromValueProvider() - { - // Arrange - var action = GetActionModel(typeof(ControllerWithBoundProperty), nameof(ControllerWithBoundProperty.SomeAction)); - - var provider = GetProvider(); - - // Act - provider.InferParameterModelPrefixes(action); - - // Assert - var parameter = Assert.Single(action.Parameters); - Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName); - } - - [Fact] - public void AddMultipartFormDataConsumesAttribute_NoOpsIfBehaviorIsDisabled() - { - // Arrange - var actionName = nameof(ParameterBindingController.FromFormParameter); - var action = GetActionModel(typeof(ParameterBindingController), actionName); - var options = new ApiBehaviorOptions - { - SuppressConsumesConstraintForFormFileParameters = true, - InvalidModelStateResponseFactory = _ => null, - }; - var provider = GetProvider(options); - - // Act - provider.AddMultipartFormDataConsumesAttribute(action); - - // Assert - Assert.Empty(action.Filters); - } - - [Fact] - public void AddMultipartFormDataConsumesAttribute_NoOpsIfConsumesConstraintIsAlreadyPresent() - { - // Arrange - var actionName = nameof(ParameterBindingController.ActionWithConsumesAttribute); - var action = GetActionModel(typeof(ParameterBindingController), actionName); - var options = new ApiBehaviorOptions - { - SuppressConsumesConstraintForFormFileParameters = true, - InvalidModelStateResponseFactory = _ => null, - }; - var provider = GetProvider(options); - - // Act - provider.AddMultipartFormDataConsumesAttribute(action); - - // Assert - var attribute = Assert.Single(action.Filters); - var consumesAttribute = Assert.IsType(attribute); - Assert.Equal("application/json", Assert.Single(consumesAttribute.ContentTypes)); - } - - [Fact] - public void AddMultipartFormDataConsumesAttribute_AddsConsumesAttribute_WhenActionHasFromFormFileParameter() - { - // Arrange - var actionName = nameof(ParameterBindingController.FormFileParameter); - var action = GetActionModel(typeof(ParameterBindingController), actionName); - action.Parameters[0].BindingInfo = new BindingInfo - { - BindingSource = BindingSource.FormFile, - }; - var provider = GetProvider(); - - // Act - provider.AddMultipartFormDataConsumesAttribute(action); - - // Assert - var attribute = Assert.Single(action.Filters); - var consumesAttribute = Assert.IsType(attribute); - Assert.Equal("multipart/form-data", Assert.Single(consumesAttribute.ContentTypes)); - } - - [Fact] - public void DiscoverApiConvention_DoesNotAddConventionItem_IfActionHasProducesResponseTypeAttribute() - { - // Arrange - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()); - actionModel.Filters.Add(new ProducesResponseTypeAttribute(200)); - var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; - - // Act - ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); - - // Assert - Assert.Empty(actionModel.Properties); - } - - [Fact] - public void DiscoverApiConvention_DoesNotAddConventionItem_IfActionHasProducesAttribute() - { - // Arrange - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()); - actionModel.Filters.Add(new ProducesAttribute(typeof(object))); - var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; - - // Act - ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); - - // Assert - Assert.Empty(actionModel.Properties); - } - - [Fact] - public void DiscoverApiConvention_DoesNotAddConventionItem_IfNoConventionMatches() - { - // Arrange - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.NoMatch)), - Array.Empty()); - var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; - - // Act - ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); - - // Assert - Assert.Empty(actionModel.Properties); - } - - [Fact] - public void DiscoverApiConvention_AddsConventionItem_IfConventionMatches() - { - // Arrange - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()); - var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; - - // Act - ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ApiConventionResult), kvp.Key); - Assert.NotNull(kvp.Value); - }); - } - - [Fact] - public void DiscoverApiConvention_AddsConventionItem_IfActionHasNonConventionBasedFilters() - { - // Arrange - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()); - var attributes = new[] { new ApiConventionTypeAttribute(typeof(DefaultApiConventions)) }; - - // Act - ApiBehaviorApplicationModelProvider.DiscoverApiConvention(actionModel, attributes); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ApiConventionResult), kvp.Key); - Assert.NotNull(kvp.Value); - }); - } - - [Fact] - public void DiscoverErrorResponseType_SetsProblemDetails_IfActionHasNoAttributes() - { - // Arrange - var expected = typeof(ProblemDetails); - var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() }); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()) - { - Controller = controllerModel, - }; - var provider = GetProvider(); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.Equal(expected, value.Type); - }); - } - - [Fact] - public void DiscoverErrorResponseType_DoesNotSetDefaultProblemDetailsResponse_IfSuppressMapClientErrorsIsSet() - { - // Arrange - var expected = typeof(ProblemDetails); - var controllerModel = new ControllerModel(typeof(object).GetTypeInfo(), new[] { new object() }); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()) - { - Controller = controllerModel, - }; - var provider = GetProvider(new ApiBehaviorOptions - { - InvalidModelStateResponseFactory = _ => null, - SuppressMapClientErrors = true, - }); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Empty(actionModel.Properties); - } - - [Fact] - public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnControllerAssembly() - { - // Arrange - var expected = typeof(InvalidEnumArgumentException); - var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new object() }); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()) - { - Controller = controllerModel, - }; - var provider = GetProvider(); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.Equal(expected, value.Type); - }); - } - - [Fact] - public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnController() - { - // Arrange - var expected = typeof(InvalidTimeZoneException); - var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(expected) }); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - Array.Empty()) - { - Controller = controllerModel, - }; - var provider = GetProvider(); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.Equal(expected, value.Type); - }); - } - - [Fact] - public void DiscoverErrorResponseType_UsesValueFromApiErrorTypeAttribute_SpecifiedOnAction() - { - // Arrange - var expected = typeof(InvalidTimeZoneException); - var controllerModel = new ControllerModel(typeof(TestApiConventionController).GetTypeInfo(), new[] { new ProducesErrorResponseTypeAttribute(typeof(Guid)) }); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - new[] { new ProducesErrorResponseTypeAttribute(expected) }) - { - Controller = controllerModel, - }; - var provider = GetProvider(); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.Equal(expected, value.Type); - }); - } - - [Fact] - public void DiscoverErrorResponseType_AllowsVoidsType() - { - // Arrange - var expected = typeof(void); - var actionModel = new ActionModel( - typeof(TestApiConventionController).GetMethod(nameof(TestApiConventionController.Delete)), - new[] { new ProducesErrorResponseTypeAttribute(expected) }); - var provider = GetProvider(); - - // Act - provider.DiscoverErrorResponseType(actionModel); - - // Assert - Assert.Collection( - actionModel.Properties, - kvp => - { - Assert.Equal(typeof(ProducesErrorResponseTypeAttribute), kvp.Key); - var value = Assert.IsType(kvp.Value); - Assert.Equal(expected, value.Type); - }); - } - - [Fact] - public void OnProvidersExecuting_AddsClientErrorResultFilter() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - var provider = GetProvider(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); - Assert.Single(actionModel.Filters.OfType()); - } - - [Fact] - public void OnProvidersExecuting_DoesNotAddClientErrorResultFilter_IfFeatureIsDisabled() - { - // Arrange - var context = GetContext(typeof(TestApiController)); - var options = new ApiBehaviorOptions - { - SuppressMapClientErrors = true, - InvalidModelStateResponseFactory = _ => null, - }; - var provider = GetProvider(options); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - var actionModel = Assert.Single(Assert.Single(context.Result.Controllers).Actions); - Assert.Empty(actionModel.Filters.OfType()); - } - - // A dynamically generated type in an assembly that has an ApiConventionAttribute. - private static TypeBuilder CreateTestControllerType() - { - var attributeBuilder = new CustomAttributeBuilder( - typeof(ApiConventionTypeAttribute).GetConstructor(new[] { typeof(Type) }), - new[] { typeof(DefaultApiConventions) }); - - var assemblyName = new AssemblyName("TestAssembly"); - var assemblyBuilder = AssemblyBuilder.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Run); - assemblyBuilder.SetCustomAttribute(attributeBuilder); - - var module = assemblyBuilder.DefineDynamicModule(assemblyName.Name); - var controllerType = module.DefineType("TestController"); - return controllerType; - } - - private static ApiBehaviorApplicationModelProvider GetProvider( - ApiBehaviorOptions options = null, - IModelMetadataProvider modelMetadataProvider = null) - { - options = options ?? new ApiBehaviorOptions - { - InvalidModelStateResponseFactory = _ => null, - }; - var optionsAccessor = Options.Create(options); - - var loggerFactory = NullLoggerFactory.Instance; - modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); - return new ApiBehaviorApplicationModelProvider( - optionsAccessor, - modelMetadataProvider, - Mock.Of(), - loggerFactory); - } - - private static ApplicationModelProviderContext GetContext( - Type type, - IModelMetadataProvider modelMetadataProvider = null) - { - var context = new ApplicationModelProviderContext(new[] { type.GetTypeInfo() }); - var mvcOptions = Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }); - modelMetadataProvider = modelMetadataProvider ?? new EmptyModelMetadataProvider(); - var provider = new DefaultApplicationModelProvider(mvcOptions, modelMetadataProvider); - provider.OnProvidersExecuting(context); - - return context; - } - - private static ControllerModel GetControllerModel(Type controllerType) - { - var context = GetContext(controllerType); - return Assert.Single(context.Result.Controllers); - } - - private static ActionModel GetActionModel(Type controllerType, string actionName) - { - var context = GetContext(controllerType); - var controller = Assert.Single(context.Result.Controllers); - return Assert.Single(controller.Actions, m => m.ActionName == actionName); - } - - private static ParameterModel GetParameterModel(Type controllerType, string actionName) - { - var action = GetActionModel(controllerType, actionName); - return Assert.Single(action.Parameters); - } - - private static ParameterModel GetParameterModel(ActionModel action) - { - return Assert.Single(action.Parameters.Where(x => typeof(T).IsAssignableFrom(x.ParameterType))); - } - - [ApiController] - [Route("TestApi")] - private class TestApiController : ControllerBase - { - [HttpGet] - public IActionResult TestAction() => null; - } - - private class SimpleController : ControllerBase - { - public IActionResult ActionWithoutFilter() => null; - - [TestApiBehavior] - [HttpGet("/Simple/ActionWithFilter")] - public IActionResult ActionWithFilter() => null; - } - - [ApiController] - private class ActionsWithoutAttributeRouting - { - public IActionResult Index() => null; - } - - [AttributeUsage(AttributeTargets.Method)] - private class TestApiBehavior : Attribute, IApiBehaviorMetadata - { - } - - [ApiController] - [Route("[controller]/[action]")] - private class ParameterBindingController - { - [HttpGet("{parameter}")] - public IActionResult ActionWithBoundParameter([FromBody] object parameter) => null; - - [HttpGet("{id}")] - public IActionResult SimpleRouteToken(int id) => null; - - [HttpPost("optional/{id?}")] - public IActionResult OptionalRouteToken(int id) => null; - - [HttpDelete("delete-by-status/{status:int?}")] - public IActionResult ConstrainedRouteToken(object status) => null; - - [HttpPut("/absolute-route/{status:int}")] - public IActionResult AbsoluteRoute(object status) => null; - - [HttpPost("multiple/{id}")] - [HttpPut("multiple/{id}")] - public IActionResult ParameterInMultipleRoutes(int id) => null; - - [HttpPatch("patchroute")] - [HttpPost("multiple/{id}")] - [HttpPut("multiple/{id}")] - public IActionResult ParameterNotInAllRoutes(int id) => null; - - [HttpPut("put-action/{id}")] - public IActionResult ComplexTypeModel(TestModel model) => null; - - [HttpPut("put-action/{id}")] - public IActionResult SimpleTypeModel(ConvertibleFromString model) => null; - - [HttpPost("form-file")] - public IActionResult FormFileParameter(IFormFile formFile) => null; - - [HttpPost("form-file-collection")] - public IActionResult FormFileCollectionParameter(IFormFileCollection formFiles) => null; - - [HttpPost("form-file-sequence")] - public IActionResult FormFileSequenceParameter(IFormFile[] formFiles) => null; - - [HttpPost] - public IActionResult FromFormParameter([FromForm] string parameter) => null; - - [HttpPost] - [Consumes("application/json")] - public IActionResult ActionWithConsumesAttribute([FromForm] string parameter) => null; - - [HttpPut("cancellation")] - public IActionResult ComplexTypeModelWithCancellationToken(TestModel model, CancellationToken cancellationToken) => null; - - [HttpGet("parameter-with-model-binder-attribute")] - public IActionResult ModelBinderAttribute([ModelBinder(Name = "top")] int value) => null; - - [HttpGet("parameter-with-fromquery")] - public IActionResult FromQuery([FromQuery] int value) => null; - - [HttpGet("parameter-with-fromquery-and-customname")] - public IActionResult FromQueryWithCustomName([FromQuery(Name = "top")] int value) => null; - - [HttpGet("parameter-with-fromquery-on-complextype")] - public IActionResult FromQueryOnComplexType([FromQuery] GpsCoordinates gpsCoordinates) => null; - - [HttpGet("parameter-with-fromquery-on-complextype-and-customname")] - public IActionResult FromQueryOnComplexTypeWithCustomName([FromQuery(Name = "gps")] GpsCoordinates gpsCoordinates) => null; - - [HttpGet("parameter-with-fromquery-on-collection-type")] - public IActionResult FromQueryOnCollectionType([FromQuery] ICollection value) => null; - - [HttpGet("parameter-with-fromquery-on-array-type")] - public IActionResult FromQueryOnArrayType([FromQuery] int[] value) => null; - - [HttpGet("parameter-with-fromquery-on-array-type-customname")] - public IActionResult FromQueryOnArrayTypeWithCustomName([FromQuery(Name = "ids")] int[] value) => null; - - [HttpGet("parameter-with-fromroute")] - public IActionResult FromRoute([FromRoute] int value) => null; - - [HttpGet("parameter-with-fromroute-and-customname")] - public IActionResult FromRouteWithCustomName([FromRoute(Name = "top")] int value) => null; - - [HttpGet("parameter-with-fromroute-on-complextype")] - public IActionResult FromRouteOnComplexType([FromRoute] GpsCoordinates gpsCoordinates) => null; - - [HttpGet("parameter-with-fromroute-on-complextype-and-customname")] - public IActionResult FromRouteOnComplexTypeWithCustomName([FromRoute(Name = "gps")] GpsCoordinates gpsCoordinates) => null; - - [HttpGet] - public IActionResult ParameterWithRequestPredicateProvider([CustomRequestPredicateAndPropertyFilterProvider] int value) => null; - } - - private class CustomRequestPredicateAndPropertyFilterProviderAttribute : Attribute, IRequestPredicateProvider, IPropertyFilterProvider - { - public static Func RequestPredicateStatic => (c) => true; - public static Func PropertyFilterStatic => (c) => true; - - public Func RequestPredicate => RequestPredicateStatic; - - public Func PropertyFilter => PropertyFilterStatic; - } - - [ApiController] - [Route("[controller]/[action]")] - private class ModelBinderOnParameterController - { - [HttpGet] - public IActionResult ModelBinderAttributeWithExplicitModelName([ModelBinder(Name = "top")] int value) => null; - - [HttpGet] - public IActionResult ModelBinderType([ModelBinder(typeof(TestModelBinder))] string name) => null; - - [HttpGet] - public IActionResult ModelBinderTypeWithExplicitModelName([ModelBinder(typeof(TestModelBinder), Name = "foo")] string name) => null; - } - - [ApiController] - [Route("/route1/[controller]/[action]/{id}")] - [Route("/route2/[controller]/[action]/{id?}")] - private class ParameterInController - { - [HttpGet] - public IActionResult ActionWithoutRoute(int id) => null; - - [HttpGet("stuff/{status}")] - public IActionResult ActionWithRoute(int id) => null; - - [HttpGet("/absolute-route")] - public IActionResult AbsoluteRoute(int id) => null; - - [HttpPut] - [HttpPost("stuff/{status}")] - public IActionResult MultipleRoute(int id) => null; - - [HttpPut] - [HttpPost("~/stuff/{status}")] - public IActionResult MultipleRouteWithOverride(int id) => null; - } - - [ApiController] - private class ParameterBindingNoRoutesOnController - { - [HttpGet("{parameter}")] - public IActionResult SimpleRoute(int parameter) => null; - - [HttpGet] - public IActionResult NoRouteTemplate(int id) => null; - - [HttpPost("multiple/{id}")] - [HttpPut("multiple/{id}")] - public IActionResult ParameterInMultipleRoutes(int id) => null; - } - - private class TestModel { } - - [TypeConverter(typeof(ConvertibleFromStringConverter))] - private class ConvertibleFromString { } - - private class ConvertibleFromStringConverter : TypeConverter - { - public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) - => sourceType == typeof(string); - } - - [ApiController] - private class ControllerWithBoundProperty - { - [FromQuery] - public TestModel TestProperty { get; set; } - - public IActionResult SomeAction([FromQuery] TestModel test) => null; - } - - [ApiController] - private class ControllerWithBoundCollectionProperty - { - [FromQuery] - public List TestProperty { get; set; } - - public IActionResult SomeAction([FromQuery] List test) => null; - } - - private class Car { } - - [ApiController] - private class ControllerWithMultipleInferredFromBodyParameters - { - [HttpGet("test")] - public IActionResult Action(TestModel a, Car b) => null; - } - - [ApiController] - private class ControllerWithMultipleInferredOrSpecifiedFromBodyParameters - { - [HttpGet("test")] - public IActionResult Action(TestModel a, [FromBody] int b) => null; - } - - [ApiController] - private class ControllerWithMultipleFromBodyParameters - { - [HttpGet("test")] - public IActionResult Action([FromBody] decimal a, [FromBody] int b) => null; - } - - [ApiController] - private class ParameterWithBindingInfo - { - [HttpGet("test")] - public IActionResult Action([ModelBinder(typeof(object))] Car car) => null; - } - - private class TestApiConventionController - { - public IActionResult NoMatch() => null; - - public IActionResult Delete(int id) => null; - } - - private class GpsCoordinates - { - public long Latitude { get; set; } - public long Longitude { get; set; } - } - - private class TestModelBinder : IModelBinder - { - public Task BindModelAsync(ModelBindingContext bindingContext) - { - throw new NotImplementedException(); - } - } - } -} From a73d073eeab8266930523246b79e4b561111aec1 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 6 Sep 2018 11:17:01 -0700 Subject: [PATCH 238/316] Allow ApiControlelrAttribute to be applied to assemblies Fixes #7343 --- .../ApiControllerFacts.cs | 3 ++- .../ApiControllerAttribute.cs | 14 ++++++++----- .../ApiBehaviorApplicationModelProvider.cs | 15 ++++++++++++- .../ApiControllerFactsTest.cs | 21 +++++++++++++++++-- ...rnsTrue_IfAttributeIsDeclaredOnAssembly.cs | 11 ++++++++++ 5 files changed, 55 insertions(+), 9 deletions(-) create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs index 84534a5d9f..d010d58894 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs @@ -25,7 +25,8 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return false; } - if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true)) + if (!method.ContainingType.HasAttribute(symbolCache.IApiBehaviorMetadata, inherit: true) && + !method.ContainingAssembly.HasAttribute(symbolCache.IApiBehaviorMetadata)) { return false; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs index 0f1f1627bf..0be60296de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs @@ -2,16 +2,20 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using Microsoft.AspNetCore.Mvc.Internal; namespace Microsoft.AspNetCore.Mvc { /// - /// Indicates that a type and all derived types are used to serve HTTP API responses. The presence of - /// this attribute can be used to target conventions, filters and other behaviors based on the purpose - /// of the controller. + /// Indicates that a type and all derived types are used to serve HTTP API responses. + /// + /// Controllers decorated with this attribute are configured with features and behavior targeted at improving the + /// developer experience for building APIs. + /// + /// + /// When decorated on an assembly, all controllers in the assembly will be treated as controllers with API behavior. + /// /// - [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] + [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class ApiControllerAttribute : ControllerAttribute, IApiBehaviorMetadata { } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs index 54f24f8bb2..62831919de 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; @@ -75,7 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { foreach (var controller in context.Result.Controllers) { - if (!controller.Attributes.OfType().Any()) + if (!IsApiController(controller)) { continue; } @@ -123,5 +124,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels return false; } } + + private static bool IsApiController(ControllerModel controller) + { + if (controller.Attributes.OfType().Any()) + { + return true; + } + + var controllerAssembly = controller.ControllerType.Assembly; + var assemblyAttributes = controllerAssembly.GetCustomAttributes(); + return assemblyAttributes.OfType().Any(); + } } } diff --git a/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs index f096f51455..e0875d0f93 100644 --- a/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiControllerFactsTest; using Microsoft.CodeAnalysis; using Xunit; @@ -110,9 +111,25 @@ namespace TestNamespace Assert.True(result); } - private Task GetCompilation() + [Fact] + public async Task IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly() { - var testSource = MvcTestSource.Read(GetType().Name, "TestFile"); + // Arrange + var compilation = await GetCompilation(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly)); + var symbolCache = new ApiControllerSymbolCache(compilation); + var type = compilation.GetTypeByMetadataName(typeof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController).FullName); + var method = (IMethodSymbol)type.GetMembers(nameof(IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController.Action)).First(); + + // Act + var result = ApiControllerFacts.IsApiControllerAction(symbolCache, method); + + // Assert + Assert.True(result); + } + + private Task GetCompilation(string testFile = "TestFile") + { + var testSource = MvcTestSource.Read(GetType().Name, testFile); var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); return project.GetCompilationAsync(); diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs new file mode 100644 index 0000000000..a8b5d0ee8b --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs @@ -0,0 +1,11 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ApiController] + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ApiControllerFactsTest +{ + public class IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssemblyController : ControllerBase + { + public IActionResult Action() => null; + } +} From cb88e906b261536f7705464faafa7d55aa8ffa9d Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 7 Sep 2018 15:58:21 -0700 Subject: [PATCH 239/316] Allow ProducesAttribute to apply along with conventions Fixes #8389 --- .../ApiResponseTypeProvider.cs | 31 ++++- ...ApiConventionApplicationModelConvention.cs | 6 - .../ApiVisibilityConvention.cs | 0 .../ClientErrorResultFilterConvention.cs | 2 +- ...onstraintForFormFileParameterConvention.cs | 2 +- .../InferParameterBindingInfoConvention.cs | 2 +- .../InvalidModelStateFilterConvention.cs | 0 ....cs => RouteTokenTransformerConvention.cs} | 0 .../ApiResponseTypeProviderTest.cs | 113 ++++++++++++++++++ ...onventionApplicationModelConventionTest.cs | 32 ----- .../ApiVisibilityConventionTest.cs | 0 .../ClientErrorResultFilterConventionTest.cs | 0 ...raintForFormFileParameterConventionTest.cs | 0 ...InferParameterBindingInfoConventionTest.cs | 4 +- .../InvalidModelStateFilterConventionTest.cs | 2 +- .../RouteTokenTransformerConventionTest.cs | 4 +- .../ApiExplorerTest.cs | 35 ++++++ ...ResponseTypeWithApiConventionController.cs | 6 +- 18 files changed, 185 insertions(+), 54 deletions(-) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/ApiConventionApplicationModelConvention.cs (94%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/ApiVisibilityConvention.cs (100%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/ClientErrorResultFilterConvention.cs (98%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/ConsumesConstraintForFormFileParameterConvention.cs (96%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/InferParameterBindingInfoConvention.cs (99%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ApiBehaviorConventions => }/InvalidModelStateFilterConvention.cs (100%) rename src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/{ParameterTransformerConvention.cs => RouteTokenTransformerConvention.cs} (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/ApiConventionApplicationModelConventionTest.cs (86%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/ApiVisibilityConventionTest.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/ClientErrorResultFilterConventionTest.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/ConsumesConstraintForFormFileParameterConventionTest.cs (100%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/InferParameterBindingInfoConventionTest.cs (99%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/{ApiBehaviorConventions => }/InvalidModelStateFilterConventionTest.cs (99%) rename test/Microsoft.AspNetCore.Mvc.Core.Test/{ApplicationModel => ApplicationModels}/RouteTokenTransformerConventionTest.cs (96%) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index d2eec34199..cd42b8a157 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -38,12 +38,12 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer var runtimeReturnType = GetRuntimeReturnType(declaredReturnType); var responseMetadataAttributes = GetResponseMetadataAttributes(action); - if (responseMetadataAttributes.Count == 0 && + if (!HasSignificantMetadataProvider(responseMetadataAttributes) && action.Properties.TryGetValue(typeof(ApiConventionResult), out var result)) { // Action does not have any conventions. Use conventions on it if present. var apiConventionResult = (ApiConventionResult)result; - responseMetadataAttributes = apiConventionResult.ResponseMetadataProviders; + responseMetadataAttributes.AddRange(apiConventionResult.ResponseMetadataProviders); } var defaultErrorType = typeof(void); @@ -56,11 +56,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return apiResponseTypes; } - private IReadOnlyList GetResponseMetadataAttributes(ControllerActionDescriptor action) + private static List GetResponseMetadataAttributes(ControllerActionDescriptor action) { if (action.FilterDescriptors == null) { - return Array.Empty(); + return new List(); } // This technique for enumerating filters will intentionally ignore any filter that is an IFilterFactory @@ -70,7 +70,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer return action.FilterDescriptors .Select(fd => fd.Filter) .OfType() - .ToArray(); + .ToList(); } private ICollection GetApiResponseTypes( @@ -188,7 +188,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer } // Unwrap the type if it's a Task. The Task (non-generic) case was already handled. - Type unwrappedType = declaredReturnType; + var unwrappedType = declaredReturnType; if (declaredReturnType.IsGenericType && declaredReturnType.GetGenericTypeDefinition() == typeof(Task<>)) { @@ -228,5 +228,24 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer { return statusCode >= 400 && statusCode < 500; } + + private static bool HasSignificantMetadataProvider(IReadOnlyList providers) + { + for (var i = 0; i < providers.Count; i++) + { + var provider = providers[i]; + + if (provider is ProducesAttribute producesAttribute && producesAttribute.Type is null) + { + // ProducesAttribute that does not specify type is considered not significant. + continue; + } + + // Any other IApiResponseMetadataProvider is considered significant + return true; + } + + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs similarity index 94% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs index 9311ffc8e0..2ab2023a09 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs @@ -54,12 +54,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels private static void DiscoverApiConvention(ActionModel action) { - if (action.Filters.OfType().Any()) - { - // If an action already has providers, don't discover any from conventions. - return; - } - var controller = action.Controller; var apiConventionAttributes = controller.Attributes.OfType().ToArray(); if (apiConventionAttributes.Length == 0) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs similarity index 98% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs index 2b0cc744df..0fff758d4c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs @@ -27,7 +27,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels return; } - + action.Filters.Add(_filterFactory); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs similarity index 96% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs index 1f1f2cf29a..714856e11d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs @@ -9,7 +9,7 @@ using Microsoft.AspNetCore.Mvc.ModelBinding; namespace Microsoft.AspNetCore.Mvc.ApplicationModels { /// - /// An that adds a with multipart/form-data + /// An that adds a with multipart/form-data /// to controllers containing form file () parameters. /// public class ConsumesConstraintForFormFileParameterConvention : IActionModelConvention diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs similarity index 99% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs index ba7fe297d2..3ed892bc70 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs @@ -11,7 +11,7 @@ using Resources = Microsoft.AspNetCore.Mvc.Core.Resources; namespace Microsoft.AspNetCore.Mvc.ApplicationModels { /// - /// A that + /// A that /// /// infers binding sources for parameters /// for bound properties and parameters. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterTransformerConvention.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index aee457d5a6..5ea1f29b9f 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -550,6 +550,119 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } + [Fact] + public void GetApiResponseTypes_CombinesProducesAttributeAndConventions() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor(typeof(TestController), nameof(TestController.PutModel)); + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(new ProducesAttribute("application/json"), FilterScope.Controller)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(400), + new ProducesDefaultResponseTypeAttribute(), + }); + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.True(responseType.IsDefaultResponse); + Assert.Equal(typeof(ProblemDetails), responseType.Type); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(DerivedModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }, + responseType => + { + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(typeof(ProblemDetails), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [Fact] + public void GetApiResponseTypes_DoesNotCombineProducesAttributeThatSpecifiesType() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor(typeof(TestController), nameof(TestController.PutModel)); + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(new ProducesAttribute("application/json") { Type = typeof(string) }, FilterScope.Controller)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + new ProducesResponseTypeAttribute(400), + new ProducesDefaultResponseTypeAttribute(), + }); + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(string), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + + [Fact] + public void GetApiResponseTypes_DoesNotCombineProducesResponseTypeAttributeThatSpecifiesStatusCode() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor(typeof(TestController), nameof(TestController.PutModel)); + actionDescriptor.Properties[typeof(ApiConventionResult)] = new ApiConventionResult(new IApiResponseMetadataProvider[] + { + new ProducesResponseTypeAttribute(200), + }); + actionDescriptor.Properties[typeof(ProducesErrorResponseTypeAttribute)] = new ProducesErrorResponseTypeAttribute(typeof(ProblemDetails)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(DerivedModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => Assert.Equal("application/json", format.MediaType)); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs similarity index 86% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs index 643b1fbd60..d658c7dc73 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiConventionApplicationModelConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs @@ -15,38 +15,6 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { public class ApiConventionApplicationModelConventionTest { - [Fact] - public void Apply_DoesNotAddConventionItem_IfActionHasProducesResponseTypeAttribute() - { - // Arrange - var actionModel = GetActionModel(nameof(TestController.Delete)); - actionModel.Filters.Add(new ProducesResponseTypeAttribute(200)); - - var convention = GetConvention(); - - // Act - convention.Apply(actionModel); - - // Assert - Assert.DoesNotContain(typeof(ApiConventionResult), actionModel.Properties.Keys); - } - - [Fact] - public void Apply_DoesNotAddConventionItem_IfActionHasProducesAttribute() - { - // Arrange - var actionModel = GetActionModel(nameof(TestController.Delete)); - actionModel.Filters.Add(new ProducesAttribute(typeof(object))); - - var convention = GetConvention(); - - // Act - convention.Apply(actionModel); - - // Assert - Assert.DoesNotContain(typeof(ApiConventionResult), actionModel.Properties.Keys); - } - [Fact] public void Apply_DoesNotAddConventionItem_IfNoConventionMatches() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ApiVisibilityConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ClientErrorResultFilterConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/ConsumesConstraintForFormFileParameterConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs similarity index 99% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs index b836f3f70c..7619c52b19 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InferParameterBindingInfoConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs @@ -387,7 +387,7 @@ Environment.NewLine + "int b"; // Arrange var actionName = nameof(ParameterBindingController.ComplexTypeModelWithCancellationToken); - // Use the default set of ModelMetadataProviders so we get metadata details for CancellationToken. + // Use the default set of ModelMetadataProviders so we get metadata details for CancellationToken. var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); var context = GetContext(typeof(ParameterBindingController), modelMetadataProvider); var controllerModel = Assert.Single(context.Result.Controllers); @@ -750,7 +750,7 @@ Environment.NewLine + "int b"; } private static ActionModel GetActionModel( - Type controllerType, + Type controllerType, string actionName, IModelMetadataProvider modelMetadataProvider = null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs similarity index 99% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs index 080905cb4e..c53357d4f1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorConventions/InvalidModelStateFilterConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels Assert.Single(action.Filters.OfType()); } - + private static ActionModel GetActionModel() { var action = new ActionModel(typeof(object).GetMethods()[0], new object[0]); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs similarity index 96% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs index 243ce755c6..aabefa456e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModel/RouteTokenTransformerConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs @@ -2,14 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Collections.Generic; using System.Reflection; -using System.Text; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Routing; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModel +namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels { public class RouteTokenTransformerConventionTest { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index a726552c73..a4db6dd1ee 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -1277,6 +1277,41 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests }); } + [Fact] + public async Task ApiConvention_ForPostActionWithProducesAttribute() + { + // Arrange + var expectedMediaTypes = new[] { "application/json", "text/json", }; + + // Act + var response = await Client.PostAsync( + $"ApiExplorerResponseTypeWithApiConventionController/PostWithProduces", + new StringContent(string.Empty)); + var responseBody = await response.EnsureSuccessStatusCode().Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject>(responseBody); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.True(responseType.IsDefaultResponse); + }, + responseType => + { + Assert.Equal(typeof(void).FullName, responseType.ResponseType); + Assert.Equal(201, responseType.StatusCode); + Assert.Empty(responseType.ResponseFormats); + }, + responseType => + { + Assert.Equal(typeof(ProblemDetails).FullName, responseType.ResponseType); + Assert.Equal(400, responseType.StatusCode); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }); + } + [Fact] public async Task ApiConvention_ForPutActionThatMatchesConvention() { diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs index 8d831395be..dcfb5a9892 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs @@ -27,6 +27,10 @@ namespace ApiExplorerWebSite [ProducesResponseType(403)] public IActionResult PostWithConventions() => null; + [HttpPost] + [Produces("application/json", "text/json")] + public IActionResult PostWithProduces(Product p) => null; + [HttpPost] public Task PostTaskOfProduct(Product p) => null; @@ -47,4 +51,4 @@ namespace ApiExplorerWebSite [ProducesResponseType(409)] public static void CustomConventionMethod() { } } -} \ No newline at end of file +} From 35597db2777a5d6e0b3997e66fa1920e5060c76e Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 13 Sep 2018 15:16:36 -0700 Subject: [PATCH 240/316] Fix #8451 Change tokens can call into your code IMMEDIATELY when you subscribe. I reviewed our other usage of ChangeToken.OnChange in MVC and everything looks good. --- .../DefaultActionDescriptorCollectionProvider.cs | 5 +++-- .../Internal/MvcEndpointDataSource.cs | 2 ++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs index aef4292b6c..eb41cd8b7c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs @@ -33,11 +33,12 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure _actionDescriptorChangeProviders = actionDescriptorChangeProviders.ToArray(); + _lock = new object(); + + // IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately! ChangeToken.OnChange( GetCompositeChangeToken, UpdateCollection); - - _lock = new object(); } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 7f45a49b09..0c7a0f74f0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -58,6 +58,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal ConventionalEndpointInfos = new List(); + // IMPORTANT: this needs to be the last thing we do in the constructor. Change notifications can happen immediately! + // // It's possible for someone to override the collection provider without providing // change notifications. If that's the case we won't process changes. if (actions is ActionDescriptorCollectionProvider collectionProviderWithChangeToken) From 233140c33a33ed8d65c222b4ee7711cca65c5d31 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 14 Sep 2018 09:06:32 -0700 Subject: [PATCH 241/316] =?UTF-8?q?Allow=20IFormFile=20parameters=20annota?= =?UTF-8?q?ted=20with=20[FromForm]=20to=20be=20correctly=20=E2=80=A6=20(#8?= =?UTF-8?q?452)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow IFormFile parameters annotated with [FromForm] to be correctly bound in ApiControllers Fixes #8311 --- .../InferParameterBindingInfoConvention.cs | 12 ++- ...InferParameterBindingInfoConventionTest.cs | 90 ++++++++++++++++++- 2 files changed, 100 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs index 3ed892bc70..8861263faf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Linq; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing.Template; @@ -112,7 +114,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels if (property.BindingInfo != null && property.BindingInfo.BinderModelName == null && property.BindingInfo.BindingSource != null && - !property.BindingInfo.BindingSource.IsGreedy) + !property.BindingInfo.BindingSource.IsGreedy && + !IsFormFile(property.ParameterType)) { var metadata = _modelMetadataProvider.GetMetadataForProperty( controllerModel.ControllerType, @@ -133,6 +136,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels if (bindingInfo?.BindingSource != null && bindingInfo.BinderModelName == null && !bindingInfo.BindingSource.IsGreedy && + !IsFormFile(parameter.ParameterType) && IsComplexTypeParameter(parameter)) { parameter.BindingInfo.BinderModelName = string.Empty; @@ -166,5 +170,11 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels .GetMetadataForType(parameter.ParameterInfo.ParameterType); return metadata.IsComplexType && !metadata.IsCollectionType; } + + private static bool IsFormFile(Type parameterType) + { + return typeof(IFormFile).IsAssignableFrom(parameterType) || + typeof(IEnumerable).IsAssignableFrom(parameterType); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs index 7619c52b19..26da271e36 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs @@ -686,7 +686,7 @@ Environment.NewLine + "int b"; convention.InferBoundPropertyModelPrefixes(controller); // Assert - var property = Assert.Single(controller.ControllerProperties); + var property = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.TestProperty)); Assert.Equal(string.Empty, property.BindingInfo.BinderModelName); } @@ -720,6 +720,87 @@ Environment.NewLine + "int b"; Assert.Equal(string.Empty, parameter.BindingInfo.BinderModelName); } + [Fact] + public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParametersAnnotatedWithFromForm() + { + // Arrange + var action = GetActionModel( + typeof(ParameterBindingController), + nameof(ParameterBindingController.FromFormFormFileParameters), + TestModelMetadataProvider.CreateDefaultProvider()); + var convention = GetConvention(); + + // Act + convention.InferParameterModelPrefixes(action); + + // Assert + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("p1", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }, + parameter => + { + Assert.Equal("p2", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }, + parameter => + { + Assert.Equal("p3", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }); + } + + [Fact] + public void InferParameterModelPrefixes_DoesNotSetModelPrefix_ForFormFileParameters() + { + // Arrange + var action = GetActionModel( + typeof(ParameterBindingController), + nameof(ParameterBindingController.FormFileParameters), + TestModelMetadataProvider.CreateDefaultProvider()); + var convention = GetConvention(); + + // Act + convention.InferParameterModelPrefixes(action); + + // Assert + Assert.Collection( + action.Parameters, + parameter => + { + Assert.Equal("p1", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }, + parameter => + { + Assert.Equal("p2", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }, + parameter => + { + Assert.Equal("p3", parameter.Name); + Assert.Null(parameter.BindingInfo.BinderModelName); + }); + } + + [Fact] + public void InferBoundPropertyModelPrefixes_DoesNotSetModelPrefix_ForFormFileCollectionPropertiesAnnotatedWithFromForm() + { + // Arrange + var controller = GetControllerModel(typeof(ControllerWithBoundProperty)); + var convention = GetConvention(); + + // Act + convention.InferBoundPropertyModelPrefixes(controller); + + // Assert + var parameter = Assert.Single(controller.ControllerProperties, p => p.Name == nameof(ControllerWithBoundProperty.Files)); + Assert.Null(parameter.BindingInfo.BinderModelName); + } + private static InferParameterBindingInfoConvention GetConvention( IModelMetadataProvider modelMetadataProvider = null) { @@ -861,6 +942,10 @@ Environment.NewLine + "int b"; [HttpGet] public IActionResult ParameterWithRequestPredicateProvider([CustomRequestPredicateAndPropertyFilterProvider] int value) => null; + + public IActionResult FromFormFormFileParameters([FromForm] IFormFile p1, [FromForm] IFormFile[] p2, [FromForm] IFormFileCollection p3) => null; + + public IActionResult FormFileParameters(IFormFile p1, IFormFile[] p2, IFormFileCollection p3) => null; } [ApiController] @@ -955,6 +1040,9 @@ Environment.NewLine + "int b"; [FromQuery] public TestModel TestProperty { get; set; } + [FromForm] + public IList Files { get; set; } + public IActionResult SomeAction([FromQuery] TestModel test) => null; } From 4b83f7b510f22ea4acd7e383fd483301c77f5fec Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 13 Sep 2018 10:06:01 -0700 Subject: [PATCH 242/316] Make FileVersionProvider repleacable Fixes #6371 --- .../MvcRazorMvcCoreBuilderExtensions.cs | 4 +- .../Infrastructure}/CryptographyAlgorithms.cs | 4 +- .../DefaultFileVersionProvider.cs} | 67 ++--- .../TagHelperMemoryCacheProvider.cs | 2 +- .../Properties/AssemblyInfo.cs | 2 + .../Cache/CacheTagKey.cs | 1 + .../ImageTagHelper.cs | 18 +- .../LinkTagHelper.cs | 21 +- .../ScriptTagHelper.cs | 20 +- .../ViewFeatures/IFileVersionProvider.cs | 21 ++ ...t.cs => DefaultFileVersionProviderTest.cs} | 125 ++++---- .../ImageTagHelperTest.cs | 81 +++-- .../Internal/DefaultTagHelperActivatorTest.cs | 6 + .../LinkTagHelperTest.cs | 284 ++++++------------ .../ScriptTagHelperTest.cs | 257 +++++----------- 15 files changed, 350 insertions(+), 563 deletions(-) rename src/{Microsoft.AspNetCore.Mvc.TagHelpers/Internal => Microsoft.AspNetCore.Mvc.Razor/Infrastructure}/CryptographyAlgorithms.cs (88%) rename src/{Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs => Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs} (51%) create mode 100644 src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs rename test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/{Internal/FileVersionProviderTest.cs => DefaultFileVersionProviderTest.cs} (78%) diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs index 7ede8232f4..131bc13391 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Rendering; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.Language; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; @@ -228,9 +229,10 @@ namespace Microsoft.Extensions.DependencyInjection // TagHelperComponents manager services.TryAddScoped(); - // Consumed by the Cache tag helper to cache results across the lifetime of the application. + // Infrastructure for MVC TagHelpers services.TryAddSingleton(); services.TryAddSingleton(); + services.TryAddSingleton(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CryptographyAlgorithms.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs similarity index 88% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CryptographyAlgorithms.cs rename to src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs index 02c28552e1..2442315477 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CryptographyAlgorithms.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs @@ -3,9 +3,9 @@ using System.Security.Cryptography; -namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Infrastructure { - public static class CryptographyAlgorithms + internal static class CryptographyAlgorithms { public static SHA256 CreateSHA256() { diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs similarity index 51% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs index a369be3189..ae4bd7c1a6 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileVersionProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs @@ -2,59 +2,46 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.WebUtilities; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; -namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal +namespace Microsoft.AspNetCore.Mvc.Razor.Infrastructure { /// /// Provides version hash for a specified file. /// - public class FileVersionProvider + internal class DefaultFileVersionProvider : IFileVersionProvider { private const string VersionKey = "v"; private static readonly char[] QueryStringAndFragmentTokens = new [] { '?', '#' }; - private readonly IFileProvider _fileProvider; - private readonly IMemoryCache _cache; - private readonly PathString _requestPathBase; - /// - /// Creates a new instance of . - /// - /// The file provider to get and watch files. - /// where versioned urls of files are cached. - /// The base path for the current HTTP request. - public FileVersionProvider( - IFileProvider fileProvider, - IMemoryCache cache, - PathString requestPathBase) + public DefaultFileVersionProvider( + IHostingEnvironment hostingEnvironment, + TagHelperMemoryCacheProvider cacheProvider) { - if (fileProvider == null) + if (hostingEnvironment == null) { - throw new ArgumentNullException(nameof(fileProvider)); + throw new ArgumentNullException(nameof(hostingEnvironment)); } - if (cache == null) + if (cacheProvider == null) { - throw new ArgumentNullException(nameof(cache)); + throw new ArgumentNullException(nameof(cacheProvider)); } - _fileProvider = fileProvider; - _cache = cache; - _requestPathBase = requestPathBase; + FileProvider = hostingEnvironment.WebRootFileProvider; + Cache = cacheProvider.Cache; } - /// - /// Adds version query parameter to the specified file path. - /// - /// The path of the file to which version should be added. - /// Path containing the version query string. - /// - /// The version query string is appended with the key "v". - /// - public string AddFileVersionToPath(string path) + public IFileProvider FileProvider { get; } + + public IMemoryCache Cache { get; } + + public string AddFileVersionToPath(PathString requestPathBase, string path) { if (path == null) { @@ -75,22 +62,22 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal return path; } - if (_cache.TryGetValue(path, out string value)) + if (Cache.TryGetValue(path, out string value)) { return value; } var cacheEntryOptions = new MemoryCacheEntryOptions(); - cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(resolvedPath)); - var fileInfo = _fileProvider.GetFileInfo(resolvedPath); + cacheEntryOptions.AddExpirationToken(FileProvider.Watch(resolvedPath)); + var fileInfo = FileProvider.GetFileInfo(resolvedPath); if (!fileInfo.Exists && - _requestPathBase.HasValue && - resolvedPath.StartsWith(_requestPathBase.Value, StringComparison.OrdinalIgnoreCase)) + requestPathBase.HasValue && + resolvedPath.StartsWith(requestPathBase.Value, StringComparison.OrdinalIgnoreCase)) { - var requestPathBaseRelativePath = resolvedPath.Substring(_requestPathBase.Value.Length); - cacheEntryOptions.AddExpirationToken(_fileProvider.Watch(requestPathBaseRelativePath)); - fileInfo = _fileProvider.GetFileInfo(requestPathBaseRelativePath); + var requestPathBaseRelativePath = resolvedPath.Substring(requestPathBase.Value.Length); + cacheEntryOptions.AddExpirationToken(FileProvider.Watch(requestPathBaseRelativePath)); + fileInfo = FileProvider.GetFileInfo(requestPathBaseRelativePath); } if (fileInfo.Exists) @@ -104,7 +91,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal } cacheEntryOptions.SetSize(value.Length * sizeof(char)); - value = _cache.Set(path, value, cacheEntryOptions); + value = Cache.Set(path, value, cacheEntryOptions); return value; } diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs index e7deea3326..c80b9850c7 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Infrastructure /// This API supports the MVC's infrastructure and is not intended to be used /// directly from your code. This API may change in future releases. /// - public IMemoryCache Cache { get; } = new MemoryCache(new MemoryCacheOptions + public IMemoryCache Cache { get; internal set; } = new MemoryCache(new MemoryCacheOptions { SizeLimit = 10 * 1024 * 1024 // 10MB }); diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs index 73c4c5a356..0e1a8e8695 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs @@ -4,7 +4,9 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.RazorPages, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.TagHelpers, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.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")] diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs index 00647c6e2f..6db23cdcee 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Globalization; using System.Text; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Routing; diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs index 1bf569759d..97989b7754 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs @@ -7,7 +7,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; -using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -29,8 +29,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string AppendVersionAttributeName = "asp-append-version"; private const string SrcAttributeName = "src"; - private FileVersionProvider _fileVersionProvider; - /// /// Creates a new . /// @@ -55,6 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// /// The . /// The . + /// The . /// The to use. /// The . // Decorated with ActivatorUtilitiesConstructor since we want to influence tag helper activation @@ -63,12 +62,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public ImageTagHelper( IHostingEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, + IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, IUrlHelperFactory urlHelperFactory) : base(urlHelperFactory, htmlEncoder) { HostingEnvironment = hostingEnvironment; Cache = cacheProvider.Cache; + FileVersionProvider = fileVersionProvider; } /// @@ -96,6 +97,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers protected internal IMemoryCache Cache { get; } + internal IFileVersionProvider FileVersionProvider { get; private set; } + /// public override void Process(TagHelperContext context, TagHelperOutput output) { @@ -121,18 +124,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // not function properly. Src = output.Attributes[SrcAttributeName].Value as string; - output.Attributes.SetAttribute(SrcAttributeName, _fileVersionProvider.AddFileVersionToPath(Src)); + output.Attributes.SetAttribute(SrcAttributeName, FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Src)); } } private void EnsureFileVersionProvider() { - if (_fileVersionProvider == null) + if (FileVersionProvider == null) { - _fileVersionProvider = new FileVersionProvider( - HostingEnvironment.WebRootFileProvider, - Cache, - ViewContext.HttpContext.Request.PathBase); + FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService(); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs index 2b974aebbb..d58aee8711 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs @@ -12,6 +12,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -53,8 +54,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string IntegrityAttributeName = "integrity"; private static readonly Func Compare = (a, b) => a - b; - private FileVersionProvider _fileVersionProvider; - private static readonly ModeAttributes[] ModeDetails = new[] { // Regular src with file version alone new ModeAttributes(Mode.AppendVersion, new[] { AppendVersionAttributeName }), @@ -123,6 +122,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// /// The . /// + /// The . /// The . /// The . /// The . @@ -132,6 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public LinkTagHelper( IHostingEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, + IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) @@ -140,6 +141,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers HostingEnvironment = hostingEnvironment; JavaScriptEncoder = javaScriptEncoder; Cache = cacheProvider.Cache; + FileVersionProvider = fileVersionProvider; } /// @@ -242,6 +244,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers protected internal GlobbingUrlBuilder GlobbingUrlBuilder { get; set; } #pragma warning restore PUB0001 + internal IFileVersionProvider FileVersionProvider { get; private set; } + // Shared writer for determining the string content of a TagHelperAttribute's Value. private StringWriter StringWriter { @@ -299,7 +303,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var existingAttribute = output.Attributes[index]; output.Attributes[index] = new TagHelperAttribute( existingAttribute.Name, - _fileVersionProvider.AddFileVersionToPath(Href), + FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Href), existingAttribute.ValueStyle); } } @@ -468,7 +472,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var valueToWrite = fallbackHrefs[i]; if (AppendVersion == true) { - valueToWrite = _fileVersionProvider.AddFileVersionToPath(fallbackHrefs[i]); + valueToWrite = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, fallbackHrefs[i]); } // Must HTML-encode the href attribute value to ensure the written element is valid. Must also @@ -495,12 +499,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private void EnsureFileVersionProvider() { - if (_fileVersionProvider == null) + if (FileVersionProvider == null) { - _fileVersionProvider = new FileVersionProvider( - HostingEnvironment.WebRootFileProvider, - Cache, - ViewContext.HttpContext.Request.PathBase); + FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService(); } } @@ -540,7 +541,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { if (AppendVersion == true) { - hrefValue = _fileVersionProvider.AddFileVersionToPath(hrefValue); + hrefValue = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, hrefValue); } builder diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs index d8d6053d20..c9b217c1da 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs @@ -7,11 +7,11 @@ using System.IO; using System.Text.Encodings.Web; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Html; -using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Razor.TagHelpers; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers.Internal; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; @@ -44,7 +44,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private const string IntegrityAttributeName = "integrity"; private const string AppendVersionAttributeName = "asp-append-version"; private static readonly Func Compare = (a, b) => a - b; - private FileVersionProvider _fileVersionProvider; private StringWriter _stringWriter; private static readonly ModeAttributes[] ModeDetails = new[] { @@ -107,6 +106,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers /// /// The . /// The . + /// The . /// The . /// The . /// The . @@ -116,6 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers public ScriptTagHelper( IHostingEnvironment hostingEnvironment, TagHelperMemoryCacheProvider cacheProvider, + IFileVersionProvider fileVersionProvider, HtmlEncoder htmlEncoder, JavaScriptEncoder javaScriptEncoder, IUrlHelperFactory urlHelperFactory) @@ -124,6 +125,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers HostingEnvironment = hostingEnvironment; Cache = cacheProvider.Cache; JavaScriptEncoder = javaScriptEncoder; + + FileVersionProvider = fileVersionProvider; } /// @@ -201,6 +204,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers protected internal IMemoryCache Cache { get; private set; } + internal IFileVersionProvider FileVersionProvider { get; private set; } + protected JavaScriptEncoder JavaScriptEncoder { get; } // Internal for ease of use when testing. @@ -265,7 +270,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var existingAttribute = output.Attributes[index]; output.Attributes[index] = new TagHelperAttribute( existingAttribute.Name, - _fileVersionProvider.AddFileVersionToPath(Src), + FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, Src), existingAttribute.ValueStyle); } } @@ -383,7 +388,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { if (AppendVersion == true) { - srcValue = _fileVersionProvider.AddFileVersionToPath(srcValue); + srcValue = FileVersionProvider.AddFileVersionToPath(ViewContext.HttpContext.Request.PathBase, srcValue); } return srcValue; @@ -428,12 +433,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers private void EnsureFileVersionProvider() { - if (_fileVersionProvider == null) + if (FileVersionProvider == null) { - _fileVersionProvider = new FileVersionProvider( - HostingEnvironment.WebRootFileProvider, - Cache, - ViewContext.HttpContext.Request.PathBase); + FileVersionProvider = ViewContext.HttpContext.RequestServices.GetRequiredService(); } } diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs new file mode 100644 index 0000000000..1aa918c30f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs @@ -0,0 +1,21 @@ +// 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.Http; + +namespace Microsoft.AspNetCore.Mvc.ViewFeatures +{ + /// + /// Provides version hash for a specified file. + /// + public interface IFileVersionProvider + { + /// + /// Adds version query parameter to the specified file path. + /// + /// The base path for the current HTTP request. + /// The path of the file to which version should be added. + /// Path containing the version query string. + string AddFileVersionToPath(PathString requestPathBase, string path); + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs similarity index 78% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs index b2617519ba..9446703636 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/FileVersionProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs @@ -4,16 +4,18 @@ using System.Collections.Generic; using System.IO; using System.Text; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal +namespace Microsoft.AspNetCore.Mvc.TagHelpers { - public class FileVersionProviderTest + public class DefaultFileVersionProviderTest { [Theory] [InlineData("/hello/world", "/hello/world?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk")] @@ -25,13 +27,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Arrange var fileProvider = GetMockFileProvider(filePath); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var fileVersionProvider = GetFileVersionProvider(fileProvider); + var requestPath = GetRequestPathBase(); // Act - var result = fileVersionProvider.AddFileVersionToPath(filePath); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, filePath); // Assert Assert.Equal(expected, result); @@ -46,14 +46,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal path, pathStartsWithAppName: false, fileDoesNotExist: true); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var fileVersionProvider = GetFileVersionProvider(fileProvider); var mockFileProvider = Mock.Get(fileProvider); + var requestPath = GetRequestPathBase(); // Act 1 - var result = fileVersionProvider.AddFileVersionToPath(path); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, path); // Assert 1 Assert.Equal(path, result); @@ -61,7 +59,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal mockFileProvider.Verify(f => f.Watch(It.IsAny()), Times.Once()); // Act 2 - result = fileVersionProvider.AddFileVersionToPath(path); + result = fileVersionProvider.AddFileVersionToPath(requestPath, path); // Assert 2 Assert.Equal(path, result); @@ -78,14 +76,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal var fileProvider = GetMockFileProvider( "file.txt", pathStartsWithAppName); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var fileVersionProvider = GetFileVersionProvider(fileProvider); var mockFileProvider = Mock.Get(fileProvider); + var requestPath = GetRequestPathBase(); // Act 1 - var result = fileVersionProvider.AddFileVersionToPath(path); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, path); // Assert 1 Assert.Equal($"{path}?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -93,7 +89,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal mockFileProvider.Verify(f => f.Watch(It.IsAny()), Times.Once()); // Act 2 - result = fileVersionProvider.AddFileVersionToPath(path); + result = fileVersionProvider.AddFileVersionToPath(requestPath, path); // Assert 2 Assert.Equal($"{path}?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -106,13 +102,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Arrange var fileProvider = new TestFileProvider(); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var fileVersionProvider = GetFileVersionProvider(fileProvider); + var requestPath = GetRequestPathBase(); // Act 1 - File does not exist - var result = fileVersionProvider.AddFileVersionToPath("file.txt"); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, "file.txt"); // Assert 1 Assert.Equal("file.txt", result); @@ -120,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal // Act 2 - File gets added fileProvider.AddFile("file.txt", "Hello World!"); fileProvider.GetChangeToken("file.txt").HasChanged = true; - result = fileVersionProvider.AddFileVersionToPath("file.txt"); + result = fileVersionProvider.AddFileVersionToPath(requestPath, "file.txt"); // Assert 2 Assert.Equal("file.txt?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -131,14 +125,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Arrange var fileProvider = new TestFileProvider(); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var requestPath = GetRequestPathBase(); + var fileVersionProvider = GetFileVersionProvider(fileProvider); fileProvider.AddFile("file.txt", "Hello World!"); // Act 1 - File exists - var result = fileVersionProvider.AddFileVersionToPath("file.txt"); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, "file.txt"); // Assert 1 Assert.Equal("file.txt?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -146,7 +138,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal // Act 2 fileProvider.DeleteFile("file.txt"); fileProvider.GetChangeToken("file.txt").HasChanged = true; - result = fileVersionProvider.AddFileVersionToPath("file.txt"); + result = fileVersionProvider.AddFileVersionToPath(requestPath, "file.txt"); // Assert 2 Assert.Equal("file.txt", result); @@ -157,14 +149,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Arrange var fileProvider = new TestFileProvider(); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase("/wwwroot/")); + var requestPath = GetRequestPathBase("/wwwroot/"); + var fileVersionProvider = GetFileVersionProvider(fileProvider); fileProvider.AddFile("file.txt", "Hello World!"); // Act 1 - File exists - var result = fileVersionProvider.AddFileVersionToPath("/wwwroot/file.txt"); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, "/wwwroot/file.txt"); // Assert 1 Assert.Equal("/wwwroot/file.txt?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -172,7 +162,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal // Act 2 fileProvider.DeleteFile("file.txt"); fileProvider.GetChangeToken("file.txt").HasChanged = true; - result = fileVersionProvider.AddFileVersionToPath("/wwwroot/file.txt"); + result = fileVersionProvider.AddFileVersionToPath(requestPath, "/wwwroot/file.txt"); // Assert 2 Assert.Equal("/wwwroot/file.txt", result); @@ -192,14 +182,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal var fileProvider = new TestFileProvider(); fileProvider.AddFile("/hello/world", mockFile.Object); - - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var requestPath = GetRequestPathBase(); + var fileVersionProvider = GetFileVersionProvider(fileProvider); // Act - var result = fileVersionProvider.AddFileVersionToPath("/hello/world"); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, "/hello/world"); // Assert Assert.True(stream.Disposed); @@ -216,13 +203,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal { // Arrange var fileProvider = GetMockFileProvider(filePath, pathStartsWithAppBase); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase(requestPathBase)); + var requestPath = GetRequestPathBase(requestPathBase); + var fileVersionProvider = GetFileVersionProvider(fileProvider); // Act - var result = fileVersionProvider.AddFileVersionToPath(filePath); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, filePath); // Assert Assert.Equal(filePath + "?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk", result); @@ -234,13 +219,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal // Arrange var filePath = "http://contoso.com/hello/world"; var fileProvider = GetMockFileProvider(filePath, false, true); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - new MemoryCache(new MemoryCacheOptions()), - GetRequestPathBase()); + var requestPath = GetRequestPathBase(); + var fileVersionProvider = GetFileVersionProvider(fileProvider); // Act - var result = fileVersionProvider.AddFileVersionToPath(filePath); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, filePath); // Assert Assert.Equal("http://contoso.com/hello/world", result); @@ -252,15 +235,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal // Arrange var filePath = "/hello/world"; var fileProvider = GetMockFileProvider(filePath); - var memoryCache = new MemoryCache(new MemoryCacheOptions()); - memoryCache.Set(filePath, "FromCache"); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - memoryCache, - GetRequestPathBase()); + var fileVersionProvider = GetFileVersionProvider(fileProvider); + var cacheEntryOptions = new MemoryCacheEntryOptions(); + cacheEntryOptions.SetSize(1); + fileVersionProvider.Cache.Set(filePath, "FromCache", cacheEntryOptions); + var requestPath = GetRequestPathBase(); // Act - var result = fileVersionProvider.AddFileVersionToPath(filePath); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, filePath); // Assert Assert.Equal("FromCache", result); @@ -290,13 +272,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal .Returns(cacheEntry) .Verifiable(); - var fileVersionProvider = new FileVersionProvider( - fileProvider, - cache.Object, - GetRequestPathBase(requestPathBase)); + var requestPath = GetRequestPathBase(requestPathBase); + + var fileVersionProvider = GetFileVersionProvider(fileProvider, cache.Object); // Act - var result = fileVersionProvider.AddFileVersionToPath(filePath); + var result = fileVersionProvider.AddFileVersionToPath(requestPath, filePath); // Assert Assert.Equal(expected, result); @@ -305,6 +286,20 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers.Internal cache.VerifyAll(); } + private static DefaultFileVersionProvider GetFileVersionProvider( + IFileProvider fileProvider, + IMemoryCache memoryCache = null) + { + var hostingEnv = Mock.Of(e => e.WebRootFileProvider == fileProvider); + var cacheProvider = new TagHelperMemoryCacheProvider(); + if (memoryCache != null) + { + cacheProvider.Cache = memoryCache; + } + + return new DefaultFileVersionProvider(hostingEnv, cacheProvider); + } + private static IFileProvider GetMockFileProvider( string filePath, bool pathStartsWithAppName = false, diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs index b3787dccec..1c8a307c4a 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs @@ -19,7 +19,6 @@ using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Razor.TagHelpers; using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Primitives; using Microsoft.Extensions.WebEncoders.Testing; @@ -58,8 +57,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers outputAttributes, getChildContentAsync: (useCachedResult, encoder) => Task.FromResult( new DefaultTagHelperContent())); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var urlHelper = new Mock(); // Ensure expanded path does not look like an absolute path on Linux, avoiding @@ -72,16 +69,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers .Setup(f => f.GetUrlHelper(It.IsAny())) .Returns(urlHelper.Object); - var helper = new ImageTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - urlHelperFactory.Object) - { - ViewContext = viewContext, - AppendVersion = true, - Src = src, - }; + var helper = GetHelper(urlHelperFactory: urlHelperFactory.Object); + helper.AppendVersion = true; + helper.Src = src; // Act helper.Process(context, output); @@ -123,19 +113,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "src", "testimage.png?v=f4OxZX_x_FO5LcGBSKHWXfwtSx-j1ncoSt3SABJtkGk" } }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - - var helper = new ImageTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Src = "testimage.png", - AppendVersion = true, - }; + var helper = GetHelper(); + helper.Src = "testimage.png"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -171,12 +151,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext(); - var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Src = "/images/test-image.png", - AppendVersion = true - }; + var helper = GetHelper(); + helper.Src = "/images/test-image.png"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -207,12 +184,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext(); - var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Src = "/images/test-image.png", - AppendVersion = false - }; + var helper = GetHelper(); + helper.Src = "/images/test-image.png"; // Act helper.Process(context, output); @@ -243,12 +216,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext("/bar"); - var helper = new ImageTagHelper(hostingEnvironment, new TagHelperMemoryCacheProvider(), new HtmlTestEncoder(), MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Src = "/bar/images/image.jpg", - AppendVersion = true - }; + var helper = GetHelper(); + helper.Src = "/bar/images/image.jpg"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -281,6 +251,29 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers return viewContext; } + private static ImageTagHelper GetHelper( + IHostingEnvironment hostingEnvironment = null, + IUrlHelperFactory urlHelperFactory = null, + ViewContext viewContext = null) + { + hostingEnvironment = hostingEnvironment ?? MakeHostingEnvironment(); + urlHelperFactory = urlHelperFactory ?? MakeUrlHelperFactory(); + viewContext = viewContext ?? MakeViewContext(); + + var cacheProvider = new TagHelperMemoryCacheProvider(); + var fileVersionProvider = new DefaultFileVersionProvider(hostingEnvironment, cacheProvider); + + return new ImageTagHelper( + hostingEnvironment, + new TagHelperMemoryCacheProvider(), + fileVersionProvider, + new HtmlTestEncoder(), + urlHelperFactory) + { + ViewContext = viewContext, + }; + } + private static TagHelperContext MakeTagHelperContext( TagHelperAttributeList attributes) { diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs index bc6d1238c6..ad85133eee 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Razor.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Mvc.TagHelpers; +using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.DependencyInjection; using Moq; @@ -22,6 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal private readonly TagHelperMemoryCacheProvider CacheProvider = new TagHelperMemoryCacheProvider(); private readonly IMemoryCache MemoryCache = new MemoryCache(new MemoryCacheOptions()); private readonly IHostingEnvironment HostingEnvironment = Mock.Of(); + private readonly IFileVersionProvider FileVersionProvider = Mock.Of(); [Fact] public void ScriptTagHelper_DoesNotUseMemoryCacheInstanceFromDI() @@ -34,6 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal Assert.Same(CacheProvider.Cache, scriptTagHelper.Cache); Assert.Same(HostingEnvironment, scriptTagHelper.HostingEnvironment); + Assert.Same(FileVersionProvider, scriptTagHelper.FileVersionProvider); } [Fact] @@ -47,6 +50,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal Assert.Same(CacheProvider.Cache, linkTagHelper.Cache); Assert.Same(HostingEnvironment, linkTagHelper.HostingEnvironment); + Assert.Same(FileVersionProvider, linkTagHelper.FileVersionProvider); } [Fact] @@ -60,6 +64,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal Assert.Same(CacheProvider.Cache, imageTagHelper.Cache); Assert.Same(HostingEnvironment, imageTagHelper.HostingEnvironment); + Assert.Same(FileVersionProvider, imageTagHelper.FileVersionProvider); } private ViewContext CreateViewContext() @@ -71,6 +76,7 @@ namespace Microsoft.AspNetCore.Mvc.Razor.Internal .AddSingleton(HtmlEncoder.Default) .AddSingleton(JavaScriptEncoder.Default) .AddSingleton(Mock.Of()) + .AddSingleton(FileVersionProvider) .BuildServiceProvider(); var viewContext = new ViewContext diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs index 309371a185..ddf6ed2834 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs @@ -55,8 +55,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "href", hrefOutput }, }; var output = MakeTagHelperOutput("link", outputAttributes); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var urlHelper = new Mock(); // Ensure expanded path does not look like an absolute path on Linux, avoiding @@ -69,17 +67,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers .Setup(f => f.GetUrlHelper(It.IsAny())) .Returns(urlHelper.Object); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - urlHelperFactory.Object) - { - ViewContext = viewContext, - AppendVersion = true, - Href = href, - }; + var helper = GetHelper(urlHelperFactory: urlHelperFactory.Object); + helper.AppendVersion = true; + helper.Href = href; // Act helper.Process(context, output); @@ -163,23 +153,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("rel", new HtmlString("stylesheet")) })); var output = MakeTagHelperOutput("link", combinedOutputAttributes); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - FallbackHref = "test.css", - FallbackTestClass = "hidden", - FallbackTestProperty = "visibility", - FallbackTestValue = "hidden", - Href = "test.css", - }; + var helper = GetHelper(); + helper.FallbackHref = "test.css"; + helper.FallbackTestClass = "hidden"; + helper.FallbackTestProperty = "visibility"; + helper.FallbackTestValue = "hidden"; + helper.Href = "test.css"; + var expectedAttributes = new TagHelperAttributeList(output.Attributes); expectedAttributes.Add(new TagHelperAttribute("href", "test.css")); @@ -320,8 +301,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("link"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -329,16 +308,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new[] { "/common.css" }); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - GlobbingUrlBuilder = globbingUrlBuilder.Object - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + setProperties(helper); // Act @@ -417,8 +389,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("link"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -426,16 +396,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new[] { "/common.css" }); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - GlobbingUrlBuilder = globbingUrlBuilder.Object - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; setProperties(helper); // Act @@ -469,23 +431,13 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "rel", new HtmlString("stylesheet") }, { "data-extra", new HtmlString("something") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - FallbackHref = "test.css", - FallbackTestClass = "hidden", - FallbackTestProperty = "visibility", - FallbackTestValue = "hidden", - Href = "test.css", - }; + var helper = GetHelper(); + helper.FallbackHref = "test.css"; + helper.FallbackTestClass = "hidden"; + helper.FallbackTestProperty = "visibility"; + helper.FallbackTestValue = "hidden"; + helper.Href = "test.css"; // Act helper.Process(context, output); @@ -580,18 +532,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("link"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - }; + var helper = GetHelper(); setProperties(helper); // Act @@ -610,18 +552,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(); var output = MakeTagHelperOutput("link"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - }; + var helper = GetHelper(); // Act helper.Process(context, output); @@ -650,8 +582,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -659,18 +589,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null)) .Returns(new[] { "/base.css" }); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - ViewContext = viewContext, - Href = "/css/site.css", - HrefInclude = "**/*.css", - }; + var helper = GetHelper(); + + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Href = "/css/site.css"; + helper.HrefInclude = "**/*.css"; // Act helper.Process(context, output); @@ -713,8 +636,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "literal", "all HTML encoded" }, { new TagHelperAttribute("mixed", mixed, HtmlAttributeValueStyle.SingleQuotes) }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -722,18 +643,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null)) .Returns(new[] { "/base.css" }); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - Href = "/css/site.css", - HrefInclude = "**/*.css", - ViewContext = viewContext, - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Href = "/css/site.css"; + helper.HrefInclude = "**/*.css"; // Act helper.Process(context, output); @@ -760,20 +673,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Href = "/css/site.css", - AppendVersion = true - }; + var helper = GetHelper(); + + helper.Href = "/css/site.css"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -798,20 +702,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext("/bar"); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - Href = "/bar/css/site.css", - AppendVersion = true - }; + var helper = GetHelper(); + helper.ViewContext = viewContext; + helper.Href = "/bar/css/site.css"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -850,8 +746,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -859,22 +753,14 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/fallback.css", null)) .Returns(new[] { "/fallback.css" }); - var helper = new LinkTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - AppendVersion = true, - Href = "/css/site.css", - FallbackHrefInclude = "**/fallback.css", - FallbackTestClass = "hidden", - FallbackTestProperty = "visibility", - FallbackTestValue = "hidden", - GlobbingUrlBuilder = globbingUrlBuilder.Object, - ViewContext = viewContext, - }; + var helper = GetHelper(); + helper.AppendVersion = true; + helper.Href = "/css/site.css"; + helper.FallbackHrefInclude = "**/fallback.css"; + helper.FallbackTestClass = "hidden"; + helper.FallbackTestProperty = "visibility"; + helper.FallbackTestValue = "hidden"; + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; // Act helper.Process(context, output); @@ -929,8 +815,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "mixed", mixed }, { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -938,22 +822,15 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/fallback.css", null)) .Returns(new[] { "/fallback.css" }); - var helper = new LinkTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - AppendVersion = true, - FallbackHrefInclude = "**/fallback.css", - FallbackTestClass = "hidden", - FallbackTestProperty = "visibility", - FallbackTestValue = "hidden", - GlobbingUrlBuilder = globbingUrlBuilder.Object, - Href = "/css/site.css", - ViewContext = viewContext, - }; + var helper = GetHelper(); + + helper.AppendVersion = true; + helper.FallbackHrefInclude = "**/fallback.css"; + helper.FallbackTestClass = "hidden"; + helper.FallbackTestProperty = "visibility"; + helper.FallbackTestValue = "hidden"; + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Href = "/css/site.css"; // Act helper.Process(context, output); @@ -981,8 +858,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { { "rel", new HtmlString("stylesheet") }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -990,19 +865,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.css", null)) .Returns(new[] { "/base.css" }); - var helper = new LinkTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - ViewContext = viewContext, - Href = "/css/site.css", - HrefInclude = "**/*.css", - AppendVersion = true - }; + var helper = GetHelper(); + + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Href = "/css/site.css"; + helper.HrefInclude = "**/*.css"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -1016,12 +884,36 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers content); } + private static LinkTagHelper GetHelper( + IHostingEnvironment hostingEnvironment = null, + IUrlHelperFactory urlHelperFactory = null, + ViewContext viewContext = null) + { + hostingEnvironment = hostingEnvironment ?? MakeHostingEnvironment(); + urlHelperFactory = urlHelperFactory ?? MakeUrlHelperFactory(); + viewContext = viewContext ?? MakeViewContext(); + + var memoryCacheProvider = new TagHelperMemoryCacheProvider(); + var fileVersionProvider = new DefaultFileVersionProvider(hostingEnvironment, memoryCacheProvider); + + return new LinkTagHelper( + hostingEnvironment, + memoryCacheProvider, + fileVersionProvider, + new HtmlTestEncoder(), + new JavaScriptTestEncoder(), + urlHelperFactory) + { + ViewContext = viewContext, + }; + } + private static ViewContext MakeViewContext(string requestPathBase = null) { var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); if (requestPathBase != null) { - actionContext.HttpContext.Request.PathBase = new Http.PathString(requestPathBase); + actionContext.HttpContext.Request.PathBase = new PathString(requestPathBase); } var metadataProvider = new EmptyModelMetadataProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs index eb6696292a..75f7da33a7 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs @@ -56,8 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "src", srcOutput }, }; var output = MakeTagHelperOutput("script", outputAttributes); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var urlHelper = new Mock(); // Ensure expanded path does not look like an absolute path on Linux, avoiding @@ -70,17 +68,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers .Setup(f => f.GetUrlHelper(It.IsAny())) .Returns(urlHelper.Object); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - urlHelperFactory.Object) - { - ViewContext = viewContext, - AppendVersion = true, - Src = src, - }; + var helper = GetHelper(urlHelperFactory: urlHelperFactory.Object); + helper.AppendVersion = true; + helper.Src = src; // Act helper.Process(context, output); @@ -107,7 +97,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-fallback-test", "isavailable()"), })); var tagHelperContext = MakeTagHelperContext(allAttributes); - var viewContext = MakeViewContext(); var combinedOutputAttributes = new TagHelperAttributeList( outputAttributes.Concat( new[] @@ -115,20 +104,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("data-extra", new HtmlString("something")) })); var output = MakeTagHelperOutput("script", combinedOutputAttributes); - var hostingEnvironment = MakeHostingEnvironment(); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - FallbackSrc = "~/blank.js", - FallbackTestExpression = "http://www.example.com/blank.js", - Src = "/blank.js", - }; + var helper = GetHelper(); + helper.FallbackSrc = "~/blank.js"; + helper.FallbackTestExpression = "http://www.example.com/blank.js"; + helper.Src = "/blank.js"; + var expectedAttributes = new TagHelperAttributeList(output.Attributes); expectedAttributes.Add(new TagHelperAttribute("src", "/blank.js")); @@ -301,8 +282,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("script"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -310,16 +289,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new[] { "/common.js" }); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - GlobbingUrlBuilder = globbingUrlBuilder.Object - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; setProperties(helper); // Act @@ -398,8 +369,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers // Arrange var context = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("script"); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -407,16 +376,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(It.IsAny(), It.IsAny(), It.IsAny())) .Returns(new[] { "/common.js" }); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - GlobbingUrlBuilder = globbingUrlBuilder.Object - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; setProperties(helper); // Act @@ -503,18 +464,8 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var tagHelperContext = MakeTagHelperContext(attributes); var output = MakeTagHelperOutput("script"); var logger = new Mock>(); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - }; + var helper = GetHelper(); setProperties(helper); // Act @@ -535,15 +486,7 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers var viewContext = MakeViewContext(); var output = MakeTagHelperOutput("script"); - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - }; + var helper = GetHelper(); // Act helper.Process(tagHelperContext, output); @@ -569,8 +512,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-fallback-test", "isavailable()"), }); - var viewContext = MakeViewContext(); - var output = MakeTagHelperOutput("src", attributes: new TagHelperAttributeList { @@ -578,20 +519,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("data-more", "else"), }); - var hostingEnvironment = MakeHostingEnvironment(); - - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - FallbackSrc = "~/blank.js", - FallbackTestExpression = "http://www.example.com/blank.js", - Src = "/blank.js", - }; + var helper = GetHelper(); + helper.FallbackSrc = "~/blank.js"; + helper.FallbackTestExpression = "http://www.example.com/blank.js"; + helper.Src = "/blank.js"; // Act helper.Process(tagHelperContext, output); @@ -615,8 +546,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-src-include", "**/*.js") }); var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList()); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -624,18 +553,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null)) .Returns(new[] { "/common.js" }); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - ViewContext = viewContext, - Src = "/js/site.js", - SrcInclude = "**/*.js", - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Src = "/js/site.js"; + helper.SrcInclude = "**/*.js"; // Act helper.Process(context, output); @@ -678,8 +599,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "literal", "all HTML encoded"}, { new TagHelperAttribute("mixed", mixed, HtmlAttributeValueStyle.SingleQuotes) }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -687,18 +606,10 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "**/*.js", null)) .Returns(new[] { "/common.js" }); - var helper = new ScriptTagHelper( - hostingEnvironment, - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - Src = "/js/site.js", - SrcInclude = "**/*.js", - ViewContext = viewContext, - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.Src = "/js/site.js"; + helper.SrcInclude = "**/*.js"; // Act helper.Process(context, output); @@ -722,20 +633,9 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers }); var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList()); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - AppendVersion = true, - Src = "/js/site.js", - }; + var helper = GetHelper(); + helper.Src = "/js/site.js"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -756,20 +656,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-append-version", "true") }); var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList()); - var hostingEnvironment = MakeHostingEnvironment(); var viewContext = MakeViewContext("/bar"); - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - AppendVersion = true, - Src = "/bar/js/site.js", - }; + var helper = GetHelper(viewContext: viewContext); + helper.Src = "/bar/js/site.js"; + helper.AppendVersion = true; // Act helper.Process(context, output); @@ -792,22 +683,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-append-version", "true") }); var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList()); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - ViewContext = viewContext, - FallbackSrc = "fallback.js", - FallbackTestExpression = "isavailable()", - AppendVersion = true, - Src = "/js/site.js", - }; + var helper = GetHelper(); + helper.FallbackSrc = "fallback.js"; + helper.FallbackTestExpression = "isavailable()"; + helper.AppendVersion = true; + helper.Src = "/js/site.js"; // Act helper.Process(context, output); @@ -855,22 +736,12 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers { "literal", "all HTML encoded" }, { new TagHelperAttribute("mixed", mixed, HtmlAttributeValueStyle.SingleQuotes) }, }); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - AppendVersion = true, - FallbackSrc = "fallback.js", - FallbackTestExpression = "isavailable()", - Src = "/js/site.js", - ViewContext = viewContext, - }; + var helper = GetHelper(); + helper.AppendVersion = true; + helper.FallbackSrc = "fallback.js"; + helper.FallbackTestExpression = "isavailable()"; + helper.Src = "/js/site.js"; // Act helper.Process(context, output); @@ -897,8 +768,6 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers new TagHelperAttribute("asp-append-version", "true") }); var output = MakeTagHelperOutput("script", attributes: new TagHelperAttributeList()); - var hostingEnvironment = MakeHostingEnvironment(); - var viewContext = MakeViewContext(); var globbingUrlBuilder = new Mock( new TestFileProvider(), Mock.Of(), @@ -906,19 +775,11 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers globbingUrlBuilder.Setup(g => g.BuildUrlList(null, "*.js", null)) .Returns(new[] { "/common.js" }); - var helper = new ScriptTagHelper( - MakeHostingEnvironment(), - new TagHelperMemoryCacheProvider(), - new HtmlTestEncoder(), - new JavaScriptTestEncoder(), - MakeUrlHelperFactory()) - { - GlobbingUrlBuilder = globbingUrlBuilder.Object, - ViewContext = viewContext, - SrcInclude = "*.js", - AppendVersion = true, - Src = "/js/site.js", - }; + var helper = GetHelper(); + helper.GlobbingUrlBuilder = globbingUrlBuilder.Object; + helper.SrcInclude = "*.js"; + helper.AppendVersion = true; + helper.Src = "/js/site.js"; // Act helper.Process(context, output); @@ -930,6 +791,30 @@ namespace Microsoft.AspNetCore.Mvc.TagHelpers Assert.Equal(expectedContent, content); } + private static ScriptTagHelper GetHelper( + IHostingEnvironment hostingEnvironment = null, + IUrlHelperFactory urlHelperFactory = null, + ViewContext viewContext = null) + { + hostingEnvironment = hostingEnvironment ?? MakeHostingEnvironment(); + urlHelperFactory = urlHelperFactory ?? MakeUrlHelperFactory(); + viewContext = viewContext ?? MakeViewContext(); + + var memoryCacheProvider = new TagHelperMemoryCacheProvider(); + var fileVersionProvider = new DefaultFileVersionProvider(hostingEnvironment, memoryCacheProvider); + + return new ScriptTagHelper( + hostingEnvironment, + memoryCacheProvider, + fileVersionProvider, + new HtmlTestEncoder(), + new JavaScriptTestEncoder(), + urlHelperFactory) + { + ViewContext = viewContext, + }; + } + private TagHelperContext MakeTagHelperContext( TagHelperAttributeList attributes = null, string content = null) From 8791f9ad0dfd6f65a982212e921b0ec71712a475 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 15 Sep 2018 09:14:48 +1200 Subject: [PATCH 243/316] Change test parameter transformer to slugify values (#8453) --- .../EndpointRoutingTest.cs | 38 +++++++++---------- .../ConventionalTransformerController.cs | 2 +- .../Controllers/EndpointRoutingController.cs | 8 ++-- .../ParameterTransformerController.cs | 4 +- test/WebSites/RoutingWebSite/Startup.cs | 6 +-- .../TestParameterTransformer.cs | 7 +++- 6 files changed, 34 insertions(+), 31 deletions(-) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs index 73cab5f469..daee488bae 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ParameterTransformer_TokenReplacement_Found() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/_ParameterTransformer_/_Test_"); + var response = await Client.GetAsync("http://localhost/parameter-transformer/my-action"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -30,14 +30,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var result = JsonConvert.DeserializeObject(body); Assert.Equal("ParameterTransformer", result.Controller); - Assert.Equal("Test", result.Action); + Assert.Equal("MyAction", result.Action); } [Fact] public async Task ParameterTransformer_TokenReplacement_NotFound() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ParameterTransformer/Test"); + var response = await Client.GetAsync("http://localhost/ParameterTransformer/MyAction"); // Assert Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); @@ -79,7 +79,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task AttributeRoutedAction_ParameterTransformer_Found() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/_EndpointRouting_/ParameterTransformer"); + var response = await Client.GetAsync("http://localhost/endpoint-routing/ParameterTransformer"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task AttributeRoutedAction_ParameterTransformer_LinkToSelf() { // Arrange - var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { }); + var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { }); // Act var response = await Client.GetAsync(url); @@ -118,14 +118,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("EndpointRouting", result.Controller); Assert.Equal("ParameterTransformer", result.Action); - Assert.Equal("/_EndpointRouting_/ParameterTransformer", result.Link); + Assert.Equal("/endpoint-routing/ParameterTransformer", result.Link); } [Fact] public async Task AttributeRoutedAction_ParameterTransformer_LinkWithAmbientController() { // Arrange - var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Get", id = 5 }); + var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "Get", id = 5 }); // Act var response = await Client.GetAsync(url); @@ -138,14 +138,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("EndpointRouting", result.Controller); Assert.Equal("ParameterTransformer", result.Action); - Assert.Equal("/_EndpointRouting_/5", result.Link); + Assert.Equal("/endpoint-routing/5", result.Link); } [Fact] public async Task AttributeRoutedAction_ParameterTransformer_LinkToAttributeRoutedController() { // Arrange - var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "ShowPosts", controller = "Blog" }); + var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "ShowPosts", controller = "Blog" }); // Act var response = await Client.GetAsync(url); @@ -165,7 +165,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task AttributeRoutedAction_ParameterTransformer_LinkToConventionalController() { // Arrange - var url = LinkFrom("http://localhost/_EndpointRouting_/ParameterTransformer").To(new { action = "Index", controller = "Home" }); + var url = LinkFrom("http://localhost/endpoint-routing/ParameterTransformer").To(new { action = "Index", controller = "Home" }); // Act var response = await Client.GetAsync(url); @@ -292,7 +292,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ConventionalRoutedAction_ParameterTransformer() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index"); + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -317,7 +317,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ConventionalRoutedAction_ParameterTransformer_DefaultValue() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_"); + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -332,7 +332,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ConventionalRoutedAction_ParameterTransformer_WithParam() { // Arrange & Act - var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_"); + var response = await Client.GetAsync("http://localhost/ConventionalTransformerRoute/conventional-transformer/Param/my-value"); // Assert Assert.Equal(HttpStatusCode.OK, response.StatusCode); @@ -342,14 +342,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("ConventionalTransformer", result.Controller); Assert.Equal("Param", result.Action); - Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", Assert.Single(result.ExpectedUrls)); + Assert.Equal("/ConventionalTransformerRoute/conventional-transformer/Param/my-value", Assert.Single(result.ExpectedUrls)); } [Fact] public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalController() { // Arrange - var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Index", controller = "Home" }); + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new { action = "Index", controller = "Home" }); // Act var response = await Client.GetAsync(url); @@ -368,7 +368,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ConventionalRoutedAction_ParameterTransformer_LinkToConventionalControllerWithParam() { // Arrange - var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new { action = "Param", controller = "ConventionalTransformer", param = "value" }); + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new { action = "Param", controller = "ConventionalTransformer", param = "MyValue" }); // Act var response = await Client.GetAsync(url); @@ -380,14 +380,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("ConventionalTransformer", result.Controller); Assert.Equal("Index", result.Action); - Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_/Param/_value_", result.Link); + Assert.Equal("/ConventionalTransformerRoute/conventional-transformer/Param/my-value", result.Link); } [Fact] public async Task ConventionalRoutedAction_ParameterTransformer_LinkToSelf() { // Arrange - var url = LinkFrom("http://localhost/ConventionalTransformerRoute/_ConventionalTransformer_/Index").To(new {}); + var url = LinkFrom("http://localhost/ConventionalTransformerRoute/conventional-transformer/Index").To(new {}); // Act var response = await Client.GetAsync(url); @@ -399,7 +399,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("ConventionalTransformer", result.Controller); Assert.Equal("Index", result.Action); - Assert.Equal("/ConventionalTransformerRoute/_ConventionalTransformer_", result.Link); + Assert.Equal("/ConventionalTransformerRoute/conventional-transformer", result.Link); } } } diff --git a/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs b/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs index b31b2ee8b9..07d4384ae2 100644 --- a/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs @@ -21,7 +21,7 @@ namespace RoutingWebSite public IActionResult Param(string param) { - return _generator.Generate($"/ConventionalTransformerRoute/_ConventionalTransformer_/Param/{param}"); + return _generator.Generate($"/ConventionalTransformerRoute/conventional-transformer/Param/{param}"); } } } diff --git a/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs b/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs index 1163302277..22d61bac92 100644 --- a/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Mvc; namespace RoutingWebSite { - [Route("/{controller:test-transformer}")] + [Route("/{controller:slugify}")] public class EndpointRoutingController : Controller { private readonly TestResponseGenerator _generator; @@ -21,16 +21,16 @@ namespace RoutingWebSite return _generator.Generate("/EndpointRouting/Index", "/EndpointRouting"); } - [Route("/{controller:test-transformer}/{action}")] + [Route("/{controller:slugify}/{action}")] public IActionResult ParameterTransformer() { - return _generator.Generate("/_EndpointRouting_/ParameterTransformer"); + return _generator.Generate("/endpoint-routing/ParameterTransformer"); } [Route("{id}")] public IActionResult Get(int id) { - return _generator.Generate("/_EndpointRouting_/" + id); + return _generator.Generate("/endpoint-routing/" + id); } } } \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs b/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs index 69991c60b5..e9a8a76f20 100644 --- a/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs +++ b/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs @@ -19,9 +19,9 @@ namespace RoutingWebSite _generator = generator; } - public IActionResult Test() + public IActionResult MyAction() { - return _generator.Generate("/_ParameterTransformer_/_Test_"); + return _generator.Generate("/parameter-transformer/my-action"); } } } diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 653aa4e81f..c00450c9b7 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -21,13 +21,13 @@ namespace RoutingWebSite // Add route token transformer to one controller options.Conventions.Add(new ControllerRouteTokenTransformerConvention( typeof(ParameterTransformerController), - new TestParameterTransformer())); + new SlugifyParameterTransformer())); }) .SetCompatibilityVersion(CompatibilityVersion.Latest); services .AddRouting(options => { - options.ConstraintMap["test-transformer"] = typeof(TestParameterTransformer); + options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); }); services.AddScoped(); @@ -47,7 +47,7 @@ namespace RoutingWebSite routes.MapRoute( "ConventionalTransformerRoute", - "ConventionalTransformerRoute/{controller:test-transformer}/{action=Index}/{param:test-transformer?}", + "ConventionalTransformerRoute/{controller:slugify}/{action=Index}/{param:slugify?}", defaults: null, constraints: new { controller = "ConventionalTransformer" }); diff --git a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs index 0af1f4ed7e..779d483ca6 100644 --- a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs +++ b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs @@ -1,15 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Text.RegularExpressions; using Microsoft.AspNetCore.Routing; namespace RoutingWebSite { - public class TestParameterTransformer : IParameterTransformer + public class SlugifyParameterTransformer : IParameterTransformer { public string Transform(string value) { - return "_" + value + "_"; + // Slugify value + return Regex.Replace(value, "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLower(); } } } \ No newline at end of file From 6abb4d9e8131bbebccd05e76201bd00880c24e91 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 16 Sep 2018 12:22:12 -0700 Subject: [PATCH 244/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index d5a1e4682b..8fed603b93 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview3-35202 - 2.2.0-preview1-20180907.8 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-a-preview3-parameter-transformers-16968 - 2.2.0-a-preview3-parameter-transformers-16968 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-preview3-35252 + 2.2.0-preview1-20180911.1 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 5.2.6 2.8.0 2.8.0 - 2.2.0-preview3-35202 + 2.2.0-preview3-35252 1.7.0 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 2.1.0 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 2.0.9 2.1.3 2.2.0-preview2-26905-02 - 2.2.0-preview3-35202 - 2.2.0-preview3-35202 + 2.2.0-preview3-35252 + 2.2.0-preview3-35252 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 312f82f9a5..7124f37441 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180907.8 -commithash:078918eb5c1f176ee1da351c584fb4a4d7491aa0 +version:2.2.0-preview1-20180911.1 +commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65 From c13e2498a8061e35b23f81cab6662f80b879b591 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 16 Sep 2018 22:39:20 -0700 Subject: [PATCH 245/316] Create model in `ComplexTypeModelBinder` if ANY property has a greedy binding source - #7562 part 1 --- .../Binders/ComplexTypeModelBinder.cs | 68 ++++++++----------- .../Binders/ComplexTypeModelBinderTest.cs | 47 ++++++++++--- .../ComplexTypeModelBinderIntegrationTest.cs | 56 +++++++++------ 3 files changed, 97 insertions(+), 74 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs index 3f15ffdf20..2ab1cdb70c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs @@ -216,8 +216,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders return true; } - // 2. Any of the model properties can be bound using a value provider. - if (CanValueBindAnyModelProperties(bindingContext)) + // 2. Any of the model properties can be bound. + if (CanBindAnyModelProperties(bindingContext)) { return true; } @@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders return false; } - private bool CanValueBindAnyModelProperties(ModelBindingContext bindingContext) + private bool CanBindAnyModelProperties(ModelBindingContext bindingContext) { // If there are no properties on the model, there is nothing to bind. We are here means this is not a top // level object. So we return false. @@ -235,20 +235,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders return false; } - // We want to check to see if any of the properties of the model can be bound using the value providers, - // because that's all that MutableObjectModelBinder can handle. + // We want to check to see if any of the properties of the model can be bound using the value providers or + // a greedy binder. // - // However, because a property might specify a custom binding source ([FromForm]), it's not correct - // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName), - // because that may include other value providers - that would lead us to mistakenly create the model + // Because a property might specify a custom binding source ([FromForm]), it's not correct + // for us to just try bindingContext.ValueProvider.ContainsPrefixAsync(bindingContext.ModelName); + // that may include other value providers - that would lead us to mistakenly create the model // when the data is coming from a source we should use (ex: value found in query string, but the // model has [FromForm]). // // To do this we need to enumerate the properties, and see which of them provide a binding source // through metadata, then we decide what to do. // - // If a property has a binding source, and it's a greedy source, then it's not - // allowed to come from a value provider, so we skip it. + // If a property has a binding source, and it's a greedy source, then it's always bound. // // If a property has a binding source, and it's a non-greedy source, then we'll filter the // the value providers to just that source, and see if we can find a matching prefix @@ -256,12 +255,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // // If a property does not have a binding source, then it's fair game for any value provider. // - // If any property meets the above conditions and has a value from ValueProviders, then we'll - // create the model and try to bind it. OR if ALL properties of the model have a greedy source, + // Bottom line, if any property meets the above conditions and has a value from ValueProviders, then we'll + // create the model and try to bind it. Of, if ANY properties of the model have a greedy source, // then we go ahead and create it. // - var hasBindableProperty = false; - var isAnyPropertyEnabledForValueProviderBasedBinding = false; for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) { var propertyMetadata = bindingContext.ModelMetadata.Properties[i]; @@ -270,41 +267,30 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders continue; } - hasBindableProperty = true; - - // This check will skip properties which are marked explicitly using a non value binder. + // If any property can be bound from a greedy binding source, then success. var bindingSource = propertyMetadata.BindingSource; - if (bindingSource == null || !bindingSource.IsGreedy) + if (bindingSource != null && bindingSource.IsGreedy) { - isAnyPropertyEnabledForValueProviderBasedBinding = true; + return true; + } - var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; - var modelName = ModelNames.CreatePropertyModelName( - bindingContext.ModelName, - fieldName); - - using (bindingContext.EnterNestedScope( - modelMetadata: propertyMetadata, - fieldName: fieldName, - modelName: modelName, - model: null)) + // Otherwise, check whether the (perhaps filtered) value providers have a match. + var fieldName = propertyMetadata.BinderModelName ?? propertyMetadata.PropertyName; + var modelName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, fieldName); + using (bindingContext.EnterNestedScope( + modelMetadata: propertyMetadata, + fieldName: fieldName, + modelName: modelName, + model: null)) + { + // If any property can be bound from a value provider, then success. + if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) { - // If any property can be bound from a value provider then continue. - if (bindingContext.ValueProvider.ContainsPrefix(bindingContext.ModelName)) - { - return true; - } + return true; } } } - if (hasBindableProperty && !isAnyPropertyEnabledForValueProviderBasedBinding) - { - // All the properties are marked with a non value provider based marker like [FromHeader] or - // [FromBody]. - return true; - } - _logger.CannotBindToComplexType(bindingContext); return false; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs index c76eb27c0a..7eaedfa106 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs @@ -155,10 +155,6 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders [Theory] [InlineData(typeof(TypeWithNoBinderMetadata), false)] [InlineData(typeof(TypeWithNoBinderMetadata), true)] - [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)] - [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)] - [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)] - [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)] public void CanCreateModel_CreatesModelForValueProviderBasedBinderMetadatas_IfAValueProviderProvidesValue( Type modelType, bool valueProviderProvidesValue) @@ -182,6 +178,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Equal(valueProviderProvidesValue, canCreate); } + [Theory] + [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)] + [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)] + [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)] + [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)] + public void CanCreateModel_CreatesModelForValueProviderBasedBinderMetadatas_IfPropertyHasGreedyBindingSource( + Type modelType, + bool valueProviderProvidesValue) + { + var valueProvider = new Mock(); + valueProvider + .Setup(o => o.ContainsPrefix(It.IsAny())) + .Returns(valueProviderProvidesValue); + + var bindingContext = CreateContext(GetMetadataForType(modelType)); + bindingContext.IsTopLevelObject = false; + bindingContext.ValueProvider = valueProvider.Object; + bindingContext.OriginalValueProvider = valueProvider.Object; + + var binder = CreateBinder(bindingContext.ModelMetadata); + + // Act + var canCreate = binder.CanCreateModel(bindingContext); + + // Assert + Assert.True(canCreate); + } + [Theory] [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), false)] [InlineData(typeof(TypeWithAtLeastOnePropertyMarkedUsingValueBinderMetadata), true)] @@ -214,17 +238,18 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var canCreate = binder.CanCreateModel(bindingContext); // Assert - Assert.Equal(originalValueProviderProvidesValue, canCreate); + Assert.True(canCreate); } [Theory] - [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false)] - [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true)] - [InlineData(typeof(TypeWithNoBinderMetadata), false)] - [InlineData(typeof(TypeWithNoBinderMetadata), true)] + [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), false, true)] + [InlineData(typeof(TypeWithUnmarkedAndBinderMetadataMarkedProperties), true, true)] + [InlineData(typeof(TypeWithNoBinderMetadata), false, false)] + [InlineData(typeof(TypeWithNoBinderMetadata), true, true)] public void CanCreateModel_UnmarkedProperties_UsesCurrentValueProvider( Type modelType, - bool valueProviderProvidesValue) + bool valueProviderProvidesValue, + bool expectedCanCreate) { var valueProvider = new Mock(); valueProvider @@ -247,7 +272,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var canCreate = binder.CanCreateModel(bindingContext); // Assert - Assert.Equal(valueProviderProvidesValue, canCreate); + Assert.Equal(expectedCanCreate, canCreate); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs index 9eb3af091e..8f4fc6470f 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs @@ -16,7 +16,7 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.IntegrationTests { - // Integration tests targeting the behavior of the MutableObjectModelBinder and related classes + // Integration tests targeting the behavior of the ComplexTypeModelBinder and related classes // with other model binders. public class ComplexTypeModelBinderIntegrationTest { @@ -197,10 +197,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Equal("bill", entry.RawValue); } - // We don't provide enough data in this test for the 'Person' model to be created. So even though there is - // body data in the request, it won't be used. [Fact] - public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_PartialData() + public async Task ComplexTypeModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_PartialData() { // Arrange var parameter = new ParameterDescriptor() @@ -235,22 +233,21 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType(modelBindingResult.Model); - Assert.Null(model.Customer); + Assert.NotNull(model.Customer); + Assert.Equal("1 Microsoft Way", model.Customer.Address.Street); + Assert.Equal(10, model.ProductId); - Assert.Single(modelState); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); - var entry = Assert.Single(modelState, e => e.Key == "parameter.ProductId").Value; + var entry = Assert.Single(modelState).Value; Assert.Equal("10", entry.AttemptedValue); Assert.Equal("10", entry.RawValue); } - // We don't provide enough data in this test for the 'Person' model to be created. So even though there is - // body data in the request, it won't be used. [Fact] - public async Task MutableObjectModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_NoData() + public async Task ComplexTypeModelBinder_BindsNestedPOCO_WithBodyModelBinder_WithPrefix_NoData() { // Arrange var parameter = new ParameterDescriptor() @@ -285,7 +282,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType(modelBindingResult.Model); - Assert.Null(model.Customer); + Assert.NotNull(model.Customer); + Assert.Equal("1 Microsoft Way", model.Customer.Address.Street); Assert.Empty(modelState); Assert.Equal(0, modelState.ErrorCount); @@ -630,10 +628,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Equal("bill", entry.RawValue); } - // We don't provide enough data in this test for the 'Person' model to be created. So even though there are - // form files in the request, it won't be used. [Fact] - public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_PartialData() + public async Task ComplexTypeModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_PartialData() { // Arrange var parameter = new ParameterDescriptor() @@ -668,22 +664,29 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType(modelBindingResult.Model); - Assert.Null(model.Customer); + Assert.NotNull(model.Customer); + + var document = Assert.Single(model.Customer.Documents); + Assert.Equal("text.txt", document.FileName); + using (var reader = new StreamReader(document.OpenReadStream())) + { + Assert.Equal("Hello, World!", await reader.ReadToEndAsync()); + } + Assert.Equal(10, model.ProductId); - Assert.Single(modelState); + Assert.Equal(2, modelState.Count); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); + Assert.Single(modelState, e => e.Key == "parameter.Customer.Documents"); var entry = Assert.Single(modelState, e => e.Key == "parameter.ProductId").Value; Assert.Equal("10", entry.AttemptedValue); Assert.Equal("10", entry.RawValue); } - // We don't provide enough data in this test for the 'Person' model to be created. So even though there is - // body data in the request, it won't be used. [Fact] - public async Task MutableObjectModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_NoData() + public async Task ComplexTypeModelBinder_BindsNestedPOCO_WithFormFileModelBinder_WithPrefix_NoData() { // Arrange var parameter = new ParameterDescriptor() @@ -696,7 +699,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var testContext = ModelBindingTestHelper.GetTestContext(request => { request.QueryString = new QueryString("?"); - SetFormFileBodyContent(request, "Hello, World!", "parameter.Customer.Documents"); + SetFormFileBodyContent(request, "Hello, World!", "Customer.Documents"); }); var modelState = testContext.ModelState; @@ -718,11 +721,20 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.True(modelBindingResult.IsModelSet); var model = Assert.IsType(modelBindingResult.Model); - Assert.Null(model.Customer); + Assert.NotNull(model.Customer); + + var document = Assert.Single(model.Customer.Documents); + Assert.Equal("text.txt", document.FileName); + using (var reader = new StreamReader(document.OpenReadStream())) + { + Assert.Equal("Hello, World!", await reader.ReadToEndAsync()); + } - Assert.Empty(modelState); Assert.Equal(0, modelState.ErrorCount); Assert.True(modelState.IsValid); + + var entry = Assert.Single(modelState); + Assert.Equal("Customer.Documents", entry.Key); } private class Order5 From 47d6d4e82cf94f3138a413571cfca6930d2018b9 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Tue, 18 Sep 2018 08:22:29 -0700 Subject: [PATCH 246/316] Update `FormFileModelBinder` to re-add prefix `ParameterBinder` removed incorrectly - #7562 part 2 - add `OriginalModelName` to `ModelBindingContext` nit: take VS suggestions, mostly to inline collection initialization in `FormFileModelBinderTest` --- .../ModelBinding/ModelBindingContext.cs | 6 + .../Internal/DefaultModelBindingContext.cs | 1 + .../Binders/FormFileModelBinder.cs | 15 +- .../Binders/FormFileModelBinderTest.cs | 223 +++++++++++++++++- 4 files changed, 233 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs index e133b6816e..bc362837ca 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs @@ -67,6 +67,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding /// public abstract string ModelName { get; set; } + /// + /// Gets or sets the name of the top-level model. This is not reset to when value + /// providers have no match for that model. + /// + public string OriginalModelName { get; protected set; } + /// /// Gets or sets the used to capture values /// for properties in the object graph of the model when binding. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs index 0ff9292a02..c57992ceed 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs @@ -237,6 +237,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding // Because this is the top-level context, FieldName and ModelName should be the same. FieldName = binderModelName ?? modelName, ModelName = binderModelName ?? modelName, + OriginalModelName = binderModelName ?? modelName, IsTopLevelObject = true, ModelMetadata = metadata, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs index 5a7f1157ac..76b4bf54ca 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders _logger = loggerFactory.CreateLogger(); } - + /// public async Task BindModelAsync(ModelBindingContext bindingContext) { @@ -85,6 +85,19 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders await GetFormFilesAsync(modelName, bindingContext, postedFiles); + // If ParameterBinder incorrectly overrode ModelName, fall back to OriginalModelName prefix. Comparisons + // are tedious because e.g. top-level parameter or property is named Blah and it contains a BlahBlah + // property. OriginalModelName may be null in tests. + if (postedFiles.Count == 0 && + bindingContext.OriginalModelName != null && + !string.Equals(modelName, bindingContext.OriginalModelName, StringComparison.Ordinal) && + !modelName.StartsWith(bindingContext.OriginalModelName + "[", StringComparison.Ordinal) && + !modelName.StartsWith(bindingContext.OriginalModelName + ".", StringComparison.Ordinal)) + { + modelName = ModelNames.CreatePropertyModelName(bindingContext.OriginalModelName, modelName); + await GetFormFilesAsync(modelName, bindingContext, postedFiles); + } + object value; if (bindingContext.ModelType == typeof(IFormFile)) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs index d738cdc6ff..40f025b53d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs @@ -20,8 +20,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public async Task FormFileModelBinder_SingleFile_BindSuccessful() { // Arrange - var formFiles = new FormFileCollection(); - formFiles.Add(GetMockFormFile("file", "file1.txt")); + var formFiles = new FormFileCollection + { + GetMockFormFile("file", "file1.txt") + }; var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); var bindingContext = GetBindingContext(typeof(IEnumerable), httpContext); var binder = new FormFileModelBinder(NullLoggerFactory.Instance); @@ -38,6 +40,192 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Null(entry.Metadata); } + [Fact] + public async Task FormFileModelBinder_SingleFileAtTopLevel_BindSuccessfully_WithEmptyModelName() + { + // Arrange + var formFiles = new FormFileCollection + { + GetMockFormFile("file", "file1.txt") + }; + + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var binder = new FormFileModelBinder(NullLoggerFactory.Instance); + + // Mimic ParameterBinder overwriting ModelName on top level model. In this top-level binding case, + // FormFileModelBinder uses FieldName from the get-go. (OriginalModelName will be checked but ignored.) + var bindingContext = DefaultModelBindingContext.CreateBindingContext( + new ActionContext { HttpContext = httpContext }, + Mock.Of(), + new EmptyModelMetadataProvider().GetMetadataForType(typeof(IFormFile)), + bindingInfo: null, + modelName: "file"); + bindingContext.ModelName = string.Empty; + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var entry = bindingContext.ValidationState[bindingContext.Result.Model]; + Assert.False(entry.SuppressValidation); + Assert.Equal("file", entry.Key); + Assert.Null(entry.Metadata); + } + + [Fact] + public async Task FormFileModelBinder_SingleFileWithinTopLevelPoco_BindSuccessfully() + { + // Arrange + const string propertyName = nameof(NestedFormFiles.Files); + var formFiles = new FormFileCollection + { + GetMockFormFile($"{propertyName}", "file1.txt") + }; + + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var binder = new FormFileModelBinder(NullLoggerFactory.Instance); + + // In this non-top-level binding case, FormFileModelBinder tries ModelName and succeeds. + var propertyInfo = typeof(NestedFormFiles).GetProperty(propertyName); + var metadata = new EmptyModelMetadataProvider().GetMetadataForProperty( + propertyInfo, + propertyInfo.PropertyType); + var bindingContext = DefaultModelBindingContext.CreateBindingContext( + new ActionContext { HttpContext = httpContext }, + Mock.Of(), + metadata, + bindingInfo: null, + modelName: "FileList"); + bindingContext.IsTopLevelObject = false; + bindingContext.Model = new FileList(); + bindingContext.ModelName = propertyName; + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var entry = bindingContext.ValidationState[bindingContext.Result.Model]; + Assert.False(entry.SuppressValidation); + Assert.Equal($"{propertyName}", entry.Key); + Assert.Null(entry.Metadata); + } + + [Fact] + public async Task FormFileModelBinder_SingleFileWithinTopLevelPoco_BindSuccessfully_WithShortenedModelName() + { + // Arrange + const string propertyName = nameof(NestedFormFiles.Files); + var formFiles = new FormFileCollection + { + GetMockFormFile($"FileList.{propertyName}", "file1.txt") + }; + + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var binder = new FormFileModelBinder(NullLoggerFactory.Instance); + + // Mimic ParameterBinder overwriting ModelName on top level model then ComplexTypeModelBinder entering a + // nested context for the NestedFormFiles property. In this non-top-level binding case, FormFileModelBinder + // tries ModelName then falls back to add an (OriginalModelName + ".") prefix. + var propertyInfo = typeof(NestedFormFiles).GetProperty(propertyName); + var metadata = new EmptyModelMetadataProvider().GetMetadataForProperty( + propertyInfo, + propertyInfo.PropertyType); + var bindingContext = DefaultModelBindingContext.CreateBindingContext( + new ActionContext { HttpContext = httpContext }, + Mock.Of(), + metadata, + bindingInfo: null, + modelName: "FileList"); + bindingContext.IsTopLevelObject = false; + bindingContext.Model = new FileList(); + bindingContext.ModelName = propertyName; + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var entry = bindingContext.ValidationState[bindingContext.Result.Model]; + Assert.False(entry.SuppressValidation); + Assert.Equal($"FileList.{propertyName}", entry.Key); + Assert.Null(entry.Metadata); + } + + [Fact] + public async Task FormFileModelBinder_SingleFileWithinTopLevelDictionary_BindSuccessfully() + { + // Arrange + var formFiles = new FormFileCollection + { + GetMockFormFile("[myFile]", "file1.txt") + }; + + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var binder = new FormFileModelBinder(NullLoggerFactory.Instance); + + // In this non-top-level binding case, FormFileModelBinder tries ModelName and succeeds. + var bindingContext = DefaultModelBindingContext.CreateBindingContext( + new ActionContext { HttpContext = httpContext }, + Mock.Of(), + new EmptyModelMetadataProvider().GetMetadataForType(typeof(IFormFile)), + bindingInfo: null, + modelName: "FileDictionary"); + bindingContext.IsTopLevelObject = false; + bindingContext.ModelName = "[myFile]"; + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var entry = bindingContext.ValidationState[bindingContext.Result.Model]; + Assert.False(entry.SuppressValidation); + Assert.Equal("[myFile]", entry.Key); + Assert.Null(entry.Metadata); + } + + [Fact] + public async Task FormFileModelBinder_SingleFileWithinTopLevelDictionary_BindSuccessfully_WithShortenedModelName() + { + // Arrange + var formFiles = new FormFileCollection + { + GetMockFormFile("FileDictionary[myFile]", "file1.txt") + }; + + var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); + var binder = new FormFileModelBinder(NullLoggerFactory.Instance); + + // Mimic ParameterBinder overwriting ModelName on top level model then DictionaryModelBinder entering a + // nested context for the KeyValuePair.Value property. In this non-top-level binding case, + // FormFileModelBinder tries ModelName then falls back to add an OriginalModelName prefix. + var bindingContext = DefaultModelBindingContext.CreateBindingContext( + new ActionContext { HttpContext = httpContext }, + Mock.Of(), + new EmptyModelMetadataProvider().GetMetadataForType(typeof(IFormFile)), + bindingInfo: null, + modelName: "FileDictionary"); + bindingContext.IsTopLevelObject = false; + bindingContext.ModelName = "[myFile]"; + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + + var entry = bindingContext.ValidationState[bindingContext.Result.Model]; + Assert.False(entry.SuppressValidation); + Assert.Equal("FileDictionary[myFile]", entry.Key); + Assert.Null(entry.Metadata); + } + [Fact] public async Task FormFileModelBinder_ExpectMultipleFiles_BindSuccessful() { @@ -127,8 +315,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public async Task FormFileModelBinder_ReturnsFailedResult_WhenNamesDoNotMatch() { // Arrange - var formFiles = new FormFileCollection(); - formFiles.Add(GetMockFormFile("different name", "file1.txt")); + var formFiles = new FormFileCollection + { + GetMockFormFile("different name", "file1.txt") + }; var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); var bindingContext = GetBindingContext(typeof(IFormFile), httpContext); var binder = new FormFileModelBinder(NullLoggerFactory.Instance); @@ -147,9 +337,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public async Task FormFileModelBinder_UsesFieldNameForTopLevelObject(bool isTopLevel, string expected) { // Arrange - var formFiles = new FormFileCollection(); - formFiles.Add(GetMockFormFile("FieldName", "file1.txt")); - formFiles.Add(GetMockFormFile("ModelName", "file1.txt")); + var formFiles = new FormFileCollection + { + GetMockFormFile("FieldName", "file1.txt"), + GetMockFormFile("ModelName", "file1.txt") + }; var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); var bindingContext = GetBindingContext(typeof(IFormFile), httpContext); @@ -173,8 +365,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public async Task FormFileModelBinder_ReturnsFailedResult_WithEmptyContentDisposition() { // Arrange - var formFiles = new FormFileCollection(); - formFiles.Add(new Mock().Object); + var formFiles = new FormFileCollection + { + new Mock().Object + }; var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); var bindingContext = GetBindingContext(typeof(IFormFile), httpContext); var binder = new FormFileModelBinder(NullLoggerFactory.Instance); @@ -191,8 +385,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public async Task FormFileModelBinder_ReturnsFailedResult_WithNoFileNameAndZeroLength() { // Arrange - var formFiles = new FormFileCollection(); - formFiles.Add(GetMockFormFile("file", "")); + var formFiles = new FormFileCollection + { + GetMockFormFile("file", "") + }; var httpContext = GetMockHttpContext(GetMockFormCollection(formFiles)); var bindingContext = GetBindingContext(typeof(IFormFile), httpContext); var binder = new FormFileModelBinder(NullLoggerFactory.Instance); @@ -323,5 +519,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private class FileList : List { } + + private class NestedFormFiles + { + public FileList Files { get; } + } } } From c73b13f54431e107727de3bceba746bba377f0fd Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 14 Sep 2018 11:24:09 -0700 Subject: [PATCH 247/316] Cherry-pick @pranavkm's functional test for #7562 - 30a5eda508 / origin/prkrishn/form-file-value-provider Was: Design: Use a value provider to allow nested form files Fixes https://github.com/aspnet/Mvc/issues/7562 --- .../RazorPagesWithBasePathTest.cs | 36 +++++++++++++ .../Pages/PropertyBinding/BindFormFile.cshtml | 10 ++++ .../PropertyBinding/BindFormFile.cshtml.cs | 52 +++++++++++++++++++ 3 files changed, 98 insertions(+) create mode 100644 test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml create mode 100644 test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs index 4518e7b442..3de7a91ebe 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Net; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using Xunit; @@ -623,6 +624,41 @@ Hello from /Pages/Shared/"; Assert.Equal("Value from Page", valueSetInPage); } + [Fact] + public async Task RoundTrippingFormFileInputWorks() + { + // Arrange + var url = "/PropertyBinding/BindFormFile"; + var response = await Client.GetAsync(url); + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + + var document = await response.GetHtmlDocumentAsync(); + + var property1 = document.RequiredQuerySelector("#property1").GetAttribute("name"); + var file1 = document.RequiredQuerySelector("#file1").GetAttribute("name"); + var file2 = document.RequiredQuerySelector("#file2").GetAttribute("name"); + var file3 = document.RequiredQuerySelector("#file3").GetAttribute("name"); + var antiforgeryToken = document.RetrieveAntiforgeryToken(); + + var cookie = AntiforgeryTestHelper.RetrieveAntiforgeryCookie(response); + + var content = new MultipartFormDataContent(); + content.Add(new StringContent("property1-value"), property1); + content.Add(new StringContent("test-value1"), file1, "test1.txt"); + content.Add(new StringContent("test-value2"), file3, "test2.txt"); + + var request = new HttpRequestMessage(HttpMethod.Post, url) + { + Content = content, + }; + request.Headers.Add("Cookie", cookie.Key + "=" + cookie.Value); + request.Headers.Add("RequestVerificationToken", antiforgeryToken); + + response = await Client.SendAsync(request); + + await response.AssertStatusCodeAsync(HttpStatusCode.OK); + } + private async Task AddAntiforgeryHeadersAsync(HttpRequestMessage request) { var response = await Client.GetAsync(request.RequestUri); diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml new file mode 100644 index 0000000000..f388f549db --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml @@ -0,0 +1,10 @@ +@page +@model BindFormFile +@addTagHelper "*, Microsoft.AspNetCore.Mvc.TagHelpers" + +
+ + + + +
diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs new file mode 100644 index 0000000000..ccfcc87440 --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs @@ -0,0 +1,52 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RazorPagesWebSite +{ + [BindProperties] + public class BindFormFile : PageModel + { + public string Property1 { get; set; } + + public IFormFile Form3 { get; set; } + + public FormFiles Forms { get; set; } + + public IActionResult OnPost() + { + if (string.IsNullOrEmpty(Property1)) + { + throw new Exception($"{nameof(Property1)} is not bound."); + } + + if (string.IsNullOrEmpty(Form3.Name) || Form3.Length == 0) + { + throw new Exception($"{nameof(Form3)} is not bound."); + } + + if (string.IsNullOrEmpty(Forms.Form1.Name) || Forms.Form1.Length == 0) + { + throw new Exception($"{nameof(Forms.Form1)} is not bound."); + } + + if (Forms.Form2 != null) + { + throw new Exception($"{nameof(Forms.Form2)} is bound."); + } + + return new OkResult(); + } + } + + public class FormFiles + { + public IFormFile Form1 { get; set; } + + public IFormFile Form2 { get; set; } + } +} From f7da3503d6360c7b6996871818b3873f5ed1a8d7 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 18 Sep 2018 11:25:53 -0700 Subject: [PATCH 248/316] Allow Implicit 200 status codes to match Ok result --- .../DeclaredApiResponseMetadata.cs | 10 +- .../ApiConventionAnalyzerIntegrationTest.cs | 4 + .../DeclaredApiResponseMetadataTest.cs | 169 ++++++++++++++++++ .../SymbolApiResponseMetadataProviderTest.cs | 91 ++++++++++ ...sAreReturned_ForOkResultReturningAction.cs | 19 ++ .../TryGetActualResponseMetadataTests.cs | 38 ++++ 6 files changed, 330 insertions(+), 1 deletion(-) create mode 100644 test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs index a45b1ed573..30819931d0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs @@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers internal readonly struct DeclaredApiResponseMetadata { public static DeclaredApiResponseMetadata ImplicitResponse { get; } = - new DeclaredApiResponseMetadata(statusCode: 0, attributeData: null, attributeSource: null, @implicit: true, @default: false); + new DeclaredApiResponseMetadata(statusCode: 200, attributeData: null, attributeSource: null, @implicit: true, @default: false); public static DeclaredApiResponseMetadata ForProducesResponseType(int statusCode, AttributeData attributeData, IMethodSymbol attributeSource) { @@ -41,8 +41,16 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public IMethodSymbol AttributeSource { get; } + /// + /// True if this is the implicit 200 associated with an + /// action specifying no metadata. + /// public bool IsImplicit { get; } + /// + /// True if this is from a ProducesDefaultResponseTypeAttribute. + /// Matches all failure (400 and above) status codes. + /// public bool IsDefault { get; } internal static bool Contains(IList declaredApiResponseMetadata, ActualApiResponseMetadata actualMetadata) diff --git a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index f611c8d421..5e68cdbe54 100644 --- a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -26,6 +26,10 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public Task NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes() => RunNoDiagnosticsAreReturned(); + [Fact] + public Task NoDiagnosticsAreReturned_ForOkResultReturningAction() + => RunNoDiagnosticsAreReturned(); + [Fact] public Task NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred() => RunNoDiagnosticsAreReturned(); diff --git a/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs b/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs new file mode 100644 index 0000000000..fcfc65ebef --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs @@ -0,0 +1,169 @@ +// 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.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + public class DeclaredApiResponseMetadataTest + { + private readonly ReturnStatementSyntax ReturnStatement = SyntaxFactory.ReturnStatement(); + private readonly AttributeData AttributeData = new TestAttributeData(); + + [Fact] + public void Matches_ReturnsTrue_IfDeclaredMetadataIsImplicit_AndActualMetadataIsDefaultResponse() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ImplicitResponse; + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + [Fact] + public void Matches_ReturnsTrue_IfDeclaredMetadataIsImplicit_AndActualMetadataReturns200() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ImplicitResponse; + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + [Fact] + public void Matches_ReturnsTrue_IfDeclaredMetadataIs200_AndActualMetadataIsDefaultResponse() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(200, AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + /// + /// [ProducesResponseType(201)] + /// public IActionResult SomeAction => new Model(); + /// + [Fact] + public void Matches_ReturnsTrue_IfDeclaredMetadataIs201_AndActualMetadataIsDefault() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + /// + /// [ProducesResponseType(201)] + /// public IActionResult SomeAction => Ok(new Model()); + /// + [Fact] + public void Matches_ReturnsFalse_IfDeclaredMetadataIs201_AndActualMetadataIs200() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.False(matches); + } + + [Fact] + public void Matches_ReturnsTrue_IfDeclaredMetadataAndActualMetadataHaveSameStatusCode() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(302, AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 302); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + [Theory] + [InlineData(400)] + [InlineData(409)] + [InlineData(500)] + public void Matches_ReturnsTrue_IfDeclaredMetadataIsDefault_AndActualMetadataIsErrorStatusCode(int actualStatusCode) + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, actualStatusCode); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.True(matches); + } + + [Fact] + public void Matches_ReturnsFalse_IfDeclaredMetadataIsDefault_AndActualMetadataIsNotErrorStatusCode() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 204); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.False(matches); + } + + [Fact] + public void Matches_ReturnsFalse_IfDeclaredMetadataIsDefault_AndActualMetadataIsDefaultResponse() + { + // Arrange + var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + + // Act + var matches = declaredMetadata.Matches(actualMetadata); + + // Assert + Assert.False(matches); + } + + private class TestAttributeData : AttributeData + { + protected override INamedTypeSymbol CommonAttributeClass => throw new System.NotImplementedException(); + + protected override IMethodSymbol CommonAttributeConstructor => throw new System.NotImplementedException(); + + protected override SyntaxReference CommonApplicationSyntaxReference => throw new System.NotImplementedException(); + + protected override ImmutableArray CommonConstructorArguments => throw new System.NotImplementedException(); + + protected override ImmutableArray> CommonNamedArguments => throw new System.NotImplementedException(); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index 483901ffa1..c417a199fe 100644 --- a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -1,11 +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.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; @@ -433,6 +435,95 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers Assert.True(actualResponseMetadata.Value.IsDefaultResponse); } + [Fact] + public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningOkResult() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningOkResult); + + // Act + var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.False(metadata.IsDefaultResponse); + Assert.Equal(200, metadata.StatusCode); + }); + } + + [Fact] + public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningModel() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningModel); + + // Act + var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.True(metadata.IsDefaultResponse); + }); + } + + [Fact] + public async Task TryGetActualResponseMetadata_ActionReturningNotFoundAndModel() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionReturningNotFoundAndModel); + + // Act + var (success, responseMetadatas, testSource) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.False(metadata.IsDefaultResponse); + Assert.Equal(204, metadata.StatusCode); + AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM1"], metadata.ReturnStatement.GetLocation()); + + }, + metadata => + { + Assert.True(metadata.IsDefaultResponse); + AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM2"], metadata.ReturnStatement.GetLocation()); + }); + } + + private async Task<(bool result, IList responseMetadatas, TestSource testSource)> TryGetActualResponseMetadata(string typeName, string methodName) + { + var testSource = MvcTestSource.Read(GetType().Name, "TryGetActualResponseMetadataTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await GetCompilation("TryGetActualResponseMetadataTests"); + + var type = compilation.GetTypeByMetadataName(typeName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var syntaxTree = method.DeclaringSyntaxReferences[0].SyntaxTree; + var methodSyntax = (MethodDeclarationSyntax)syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + var result = SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, CancellationToken.None, out var responseMetadatas); + + return (result, responseMetadatas, testSource); + } + private async Task RunInspectReturnStatementSyntax([CallerMemberName]string test = null) { // Arrange diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs new file mode 100644 index 0000000000..e5ae8feb2c --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + [ApiController] + public class NoDiagnosticsAreReturned_ForOkResultReturningAction : ControllerBase + { + public async Task>> Action() + { + await Task.Yield(); + var models = new List(); + + return Ok(models); + } + } + + public class NoDiagnosticsAreReturned_ForOkResultReturningActionModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs new file mode 100644 index 0000000000..38e4bc1a81 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs @@ -0,0 +1,38 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +{ + public class TryGetActualResponseMetadataController : ControllerBase + { + public async Task>> ActionWithActionResultOfTReturningOkResult() + { + await Task.Yield(); + var models = new List(); + + return Ok(models); + } + + public async Task>> ActionWithActionResultOfTReturningModel() + { + await Task.Yield(); + var models = new List(); + + return models; + } + + public async Task> ActionReturningNotFoundAndModel(int id) + { + await Task.Yield(); + + if (id == 0) + { + /*MM1*/return NoContent(); + } + + /*MM2*/return new TryGetActualResponseMetadataModel(); + } + } + + public class TryGetActualResponseMetadataModel { } +} From 9c424b7b0263dd6c89eb730c7e36b2eb6e3be5e8 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 17 Sep 2018 14:56:06 -0700 Subject: [PATCH 249/316] Use content-type specified by ProducesAttribute if no formatter supports it This allows users to use `ProducesAttribute` to specify the content-type for action results such as FileStreamResult where the result determines the content type and the specified value is informational. Fixes https://github.com/aspnet/Mvc/issues/5701 --- .../ApiResponseTypeProvider.cs | 40 ++++++++++++-- .../ApiResponseTypeProviderTest.cs | 54 +++++++++++++++++-- .../ApiExplorerTest.cs | 51 +++++++++++++++--- .../Controllers/ApiExplorerApiController.cs | 6 ++- 4 files changed, 135 insertions(+), 16 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs index cd42b8a157..0761a7950a 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs @@ -135,12 +135,33 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer if (contentTypes.Count == 0) { + // None of the IApiResponseMetadataProvider specified a content type. This is common for actions that + // specify one or more ProducesResponseType but no ProducesAttribute. In this case, formatters will participate in conneg + // and respond to the incoming request. + // Querying IApiResponseTypeMetadataProvider.GetSupportedContentTypes with "null" should retrieve all supported + // content types that each formatter may respond in. contentTypes.Add((string)null); } + var responseTypes = results.Values; + CalculateResponseFormats(responseTypes, contentTypes); + return responseTypes; + } + + private void CalculateResponseFormats(ICollection responseTypes, MediaTypeCollection declaredContentTypes) + { var responseTypeMetadataProviders = _mvcOptions.OutputFormatters.OfType(); - foreach (var apiResponse in results.Values) + // Given the content-types that were declared for this action, determine the formatters that support the content-type for the given + // response type. + // 1. Responses that do not specify an type do not have any associated content-type. This usually is meant for status-code only responses such + // as return NotFound(); + // 2. When a type is specified, use GetSupportedContentTypes to expand wildcards and get the range of content-types formatters support. + // 3. When no formatter supports the specified content-type, use the user specified value as is. This is useful in actions where the user + // dictates the content-type. + // e.g. [Produces("application/pdf")] Action() => FileStream("somefile.pdf", "applicaiton/pdf"); + + foreach (var apiResponse in responseTypes) { var responseType = apiResponse.Type; if (responseType == null || responseType == typeof(void)) @@ -150,8 +171,10 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer apiResponse.ModelMetadata = _modelMetadataProvider.GetMetadataForType(responseType); - foreach (var contentType in contentTypes) + foreach (var contentType in declaredContentTypes) { + var isSupportedContentType = false; + foreach (var responseTypeMetadataProvider in responseTypeMetadataProviders) { var formatterSupportedContentTypes = responseTypeMetadataProvider.GetSupportedContentTypes( @@ -163,6 +186,8 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer continue; } + isSupportedContentType = true; + foreach (var formatterSupportedContentType in formatterSupportedContentTypes) { apiResponse.ApiResponseFormats.Add(new ApiResponseFormat @@ -172,10 +197,17 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } } + + if (!isSupportedContentType && contentType != null) + { + // No output formatter was found that supports this content type. Add the user specified content type as-is to the result. + apiResponse.ApiResponseFormats.Add(new ApiResponseFormat + { + MediaType = contentType, + }); + } } } - - return results.Values; } private Type GetDeclaredReturnType(ControllerActionDescriptor action) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs index 5ea1f29b9f..d990cdd2ff 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs @@ -45,7 +45,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.False(responseType.IsDefaultResponse); Assert.Collection( responseType.ApiResponseFormats, - format => Assert.Equal("application/json", format.MediaType)); + format => + { + Assert.Equal("application/json", format.MediaType); + Assert.IsType(format.Formatter); + }); }, responseType => { @@ -106,7 +110,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.False(responseType.IsDefaultResponse); Assert.Collection( responseType.ApiResponseFormats, - format => Assert.Equal("application/json", format.MediaType)); + format => + { + Assert.Equal("application/json", format.MediaType); + Assert.IsType(format.Formatter); + }); }, responseType => { @@ -115,7 +123,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.False(responseType.IsDefaultResponse); Assert.Collection( responseType.ApiResponseFormats, - format => Assert.Equal("application/json", format.MediaType)); + format => + { + Assert.Equal("application/json", format.MediaType); + Assert.IsType(format.Formatter); + }); }, responseType => { @@ -156,7 +168,11 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer Assert.False(responseType.IsDefaultResponse); Assert.Collection( responseType.ApiResponseFormats, - format => Assert.Equal("application/json", format.MediaType)); + format => + { + Assert.Equal("application/json", format.MediaType); + Assert.IsType(format.Formatter); + }); }, responseType => { @@ -663,6 +679,36 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer }); } + [Fact] + public void GetApiResponseTypes_UsesContentTypeWithoutWildCard_WhenNoFormatterSupportsIt() + { + // Arrange + var actionDescriptor = GetControllerActionDescriptor(typeof(TestController), nameof(TestController.GetUser)); + actionDescriptor.FilterDescriptors.Add(new FilterDescriptor(new ProducesAttribute("application/pdf"), FilterScope.Action)); + + var provider = GetProvider(); + + // Act + var result = provider.GetApiResponseTypes(actionDescriptor); + + // Assert + Assert.Collection( + result.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(typeof(DerivedModel), responseType.Type); + Assert.False(responseType.IsDefaultResponse); + Assert.Collection( + responseType.ApiResponseFormats, + format => + { + Assert.Equal("application/pdf", format.MediaType); + Assert.Null(format.Formatter); + }); + }); + } + private static ApiResponseTypeProvider GetProvider() { var mvcOptions = new MvcOptions diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs index a4db6dd1ee..e333821046 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Net.Http; @@ -829,17 +830,27 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); var responseType = Assert.Single(description.SupportedResponseTypes); - Assert.Equal(1, responseType.ResponseFormats.Count); - - var responseFormat = responseType.ResponseFormats[0]; - Assert.Equal("application/hal+json", responseFormat.MediaType); - Assert.Equal(typeof(JsonOutputFormatter).FullName, responseFormat.FormatterType); + Assert.Collection( + responseType.ResponseFormats, + responseFormat => + { + Assert.Equal("application/hal+custom", responseFormat.MediaType); + Assert.Null(responseFormat.FormatterType); + }, + responseFormat => + { + Assert.Equal("application/hal+json", responseFormat.MediaType); + Assert.Equal(typeof(JsonOutputFormatter).FullName, responseFormat.FormatterType); + }); } [Fact] public async Task ApiExplorer_ResponseContentType_NoMatch() { - // Arrange & Act + // Arrange + var expectedMediaTypes = new[] { "application/custom", "text/hal+bson" }; + + // Act var response = await Client.GetAsync("http://localhost/ApiExplorerResponseContentType/NoMatch"); var body = await response.Content.ReadAsStringAsync(); @@ -848,7 +859,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert var description = Assert.Single(result); var responseType = Assert.Single(description.SupportedResponseTypes); - Assert.Empty(responseType.ResponseFormats); + + + Assert.Equal(typeof(Product).FullName, responseType.ResponseType); + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); } [ConditionalTheory] @@ -1147,6 +1162,28 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("multipart/form-data", requestFormat.MediaType); } + [Fact] + public async Task ApiBehavior_UsesContentTypeFromProducesAttribute_WhenNoFormatterSupportsIt() + { + // Arrange + var expectedMediaTypes = new[] { "application/pdf" }; + + // Act + var body = await Client.GetStringAsync("ApiExplorerApiController/ProducesWithUnsupportedContentType"); + var result = JsonConvert.DeserializeObject>(body); + + // Assert + var description = Assert.Single(result); + Assert.Collection( + description.SupportedResponseTypes.OrderBy(r => r.StatusCode), + responseType => + { + Assert.Equal(typeof(Stream).FullName, responseType.ResponseType); + Assert.Equal(200, responseType.StatusCode); + Assert.Equal(expectedMediaTypes, GetSortedMediaTypes(responseType)); + }); + } + [Fact] public Task ApiConvention_ForGetMethod_ReturningModel() => ApiConvention_ForGetMethod("GetProduct"); diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs index d45f0bb07a..a32ebed154 100644 --- a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs +++ b/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs @@ -1,8 +1,9 @@ // 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; +using System.IO; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; namespace ApiExplorerWebSite { @@ -27,5 +28,8 @@ namespace ApiExplorerWebSite public void ActionWithFormFileCollectionParameter(IFormFileCollection formFile) { } + + [Produces("application/pdf", Type = typeof(Stream))] + public IActionResult ProducesWithUnsupportedContentType() => null; } } \ No newline at end of file From 5c4c7467975a66cc5c1ee2b0562727b0da826a53 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Wed, 19 Sep 2018 21:55:23 -0700 Subject: [PATCH 250/316] Reaction PR from routing rename --- build/dependencies.props | 4 ++-- .../ApplicationModels/AttributeRouteModel.cs | 4 ++-- .../RouteTokenTransformerConvention.cs | 12 ++++++------ .../Internal/ControllerActionDescriptorBuilder.cs | 4 ++-- .../Internal/MvcEndpointDataSource.cs | 4 ++-- .../RouteTokenTransformerConventionTest.cs | 12 ++++++------ .../Internal/MvcEndpointDataSourceTests.cs | 6 +++--- .../ControllerRouteTokenTransformerConvention.cs | 6 +++--- .../RoutingWebSite/TestParameterTransformer.cs | 6 +++--- 9 files changed, 29 insertions(+), 29 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 8fed603b93..22824154bd 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview3-35252 2.2.0-preview3-35252 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-a-preview3-outbound-parameter-tranformer-16996 + 2.2.0-a-preview3-outbound-parameter-tranformer-16996 2.2.0-preview3-35252 2.2.0-preview3-35252 2.2.0-preview3-35252 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs index 3283a630a0..4623d67090 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs @@ -225,7 +225,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels return ReplaceTokens(template, values, routeTokenTransformer: null); } - public static string ReplaceTokens(string template, IDictionary values, IParameterTransformer routeTokenTransformer) + public static string ReplaceTokens(string template, IDictionary values, IOutboundParameterTransformer routeTokenTransformer) { var builder = new StringBuilder(); var state = TemplateParserState.Plaintext; @@ -379,7 +379,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels if (routeTokenTransformer != null) { - value = routeTokenTransformer.Transform(value); + value = routeTokenTransformer.TransformOutbound(value); } builder.Append(value); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs index d7645077af..c83ba6cc47 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs @@ -8,17 +8,17 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { /// /// An that sets attribute routing token replacement - /// to use the specified on selectors. + /// to use the specified on selectors. /// public class RouteTokenTransformerConvention : IActionModelConvention { - private readonly IParameterTransformer _parameterTransformer; + private readonly IOutboundParameterTransformer _parameterTransformer; /// - /// Creates a new instance of with the specified . + /// Creates a new instance of with the specified . /// - /// The to use with attribute routing token replacement. - public RouteTokenTransformerConvention(IParameterTransformer parameterTransformer) + /// The to use with attribute routing token replacement. + public RouteTokenTransformerConvention(IOutboundParameterTransformer parameterTransformer) { if (parameterTransformer == null) { @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { if (ShouldApply(action)) { - action.Properties[typeof(IParameterTransformer)] = _parameterTransformer; + action.Properties[typeof(IOutboundParameterTransformer)] = _parameterTransformer; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs index 174457bbcc..21ec3aee6b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs @@ -390,8 +390,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal { try { - actionDescriptor.Properties.TryGetValue(typeof(IParameterTransformer), out var transformer); - var routeTokenTransformer = transformer as IParameterTransformer; + actionDescriptor.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var transformer); + var routeTokenTransformer = transformer as IOutboundParameterTransformer; actionDescriptor.AttributeRouteInfo.Template = AttributeRouteModel.ReplaceTokens( actionDescriptor.AttributeRouteInfo.Template, diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 0c7a0f74f0..0a1aa08e24 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -277,9 +277,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Use the first transformer policy for (var k = 0; k < parameterPolicies.Count; k++) { - if (parameterPolicies[k] is IParameterTransformer parameterTransformer) + if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) { - parameterRouteValue = parameterTransformer.Transform(parameterRouteValue); + parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); break; } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs index aabefa456e..807bb4d41a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels convention.Apply(model); // Assert - Assert.True(model.Properties.TryGetValue(typeof(IParameterTransformer), out var routeTokenTransformer)); + Assert.True(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var routeTokenTransformer)); Assert.Equal(transformer, routeTokenTransformer); } @@ -68,7 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels convention.Apply(model); // Assert - Assert.False(model.Properties.TryGetValue(typeof(IParameterTransformer), out _)); + Assert.False(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out _)); } private MethodInfo GetMethodInfo() @@ -76,17 +76,17 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels return typeof(RouteTokenTransformerConventionTest).GetMethod(nameof(GetMethodInfo), BindingFlags.NonPublic | BindingFlags.Instance); } - private class TestParameterTransformer : IParameterTransformer + private class TestParameterTransformer : IOutboundParameterTransformer { - public string Transform(string value) + public string TransformOutbound(object value) { - return value; + return value?.ToString(); } } private class CustomRouteTokenTransformerConvention : RouteTokenTransformerConvention { - public CustomRouteTokenTransformerConvention(IParameterTransformer parameterTransformer) : base(parameterTransformer) + public CustomRouteTokenTransformerConvention(IOutboundParameterTransformer parameterTransformer) : base(parameterTransformer) { } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 21fd57a11b..168ae2950f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -771,11 +771,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal return dataSource; } - private class UpperCaseParameterTransform : IParameterTransformer + private class UpperCaseParameterTransform : IOutboundParameterTransformer { - public string Transform(string value) + public string TransformOutbound(object value) { - return value?.ToUpperInvariant(); + return value?.ToString().ToUpperInvariant(); } } diff --git a/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs b/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs index 37dca3f259..d2b179a25b 100644 --- a/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs +++ b/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs @@ -11,9 +11,9 @@ namespace RoutingWebSite public class ControllerRouteTokenTransformerConvention : IApplicationModelConvention { private readonly Type _controllerType; - private readonly IParameterTransformer _parameterTransformer; + private readonly IOutboundParameterTransformer _parameterTransformer; - public ControllerRouteTokenTransformerConvention(Type controllerType, IParameterTransformer parameterTransformer) + public ControllerRouteTokenTransformerConvention(Type controllerType, IOutboundParameterTransformer parameterTransformer) { if (parameterTransformer == null) { @@ -30,7 +30,7 @@ namespace RoutingWebSite { foreach (var action in controller.Actions) { - action.Properties[typeof(IParameterTransformer)] = _parameterTransformer; + action.Properties[typeof(IOutboundParameterTransformer)] = _parameterTransformer; } } } diff --git a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs index 779d483ca6..a1aa4088f6 100644 --- a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs +++ b/test/WebSites/RoutingWebSite/TestParameterTransformer.cs @@ -7,12 +7,12 @@ using Microsoft.AspNetCore.Routing; namespace RoutingWebSite { - public class SlugifyParameterTransformer : IParameterTransformer + public class SlugifyParameterTransformer : IOutboundParameterTransformer { - public string Transform(string value) + public string TransformOutbound(object value) { // Slugify value - return Regex.Replace(value, "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLower(); + return value == null ? null : Regex.Replace(value.ToString(), "([a-z])([A-Z])", "$1-$2", RegexOptions.None, TimeSpan.FromMilliseconds(100)).ToLower(); } } } \ No newline at end of file From 61386d5f674348740110b11d007c5a4e48804f8e Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 20 Sep 2018 13:21:40 -0700 Subject: [PATCH 251/316] Reference Microsoft.NET.Sdk.Razor in projects with Razor files --- benchmarkapps/BasicViews/BasicViews.csproj | 1 + benchmarkapps/RazorRendering/RazorRendering.csproj | 1 + .../ApplicationModelWebSite/ApplicationModelWebSite.csproj | 1 + test/WebSites/BasicWebSite/BasicWebSite.csproj | 1 + .../ControllersFromServicesWebSite.csproj | 1 + test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj | 1 + test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj | 1 + test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj | 1 + test/WebSites/RazorWebSite/RazorWebSite.csproj | 1 + test/WebSites/SecurityWebSite/SecurityWebSite.csproj | 1 + test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj | 1 + 11 files changed, 11 insertions(+) diff --git a/benchmarkapps/BasicViews/BasicViews.csproj b/benchmarkapps/BasicViews/BasicViews.csproj index 62dd9a8740..9db233657d 100644 --- a/benchmarkapps/BasicViews/BasicViews.csproj +++ b/benchmarkapps/BasicViews/BasicViews.csproj @@ -28,6 +28,7 @@ + diff --git a/benchmarkapps/RazorRendering/RazorRendering.csproj b/benchmarkapps/RazorRendering/RazorRendering.csproj index dbbbfc0027..fee2d62006 100644 --- a/benchmarkapps/RazorRendering/RazorRendering.csproj +++ b/benchmarkapps/RazorRendering/RazorRendering.csproj @@ -7,6 +7,7 @@ + diff --git a/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj b/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj index cfda2f9c35..212a619b2e 100644 --- a/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj +++ b/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj @@ -10,5 +10,6 @@ + diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/test/WebSites/BasicWebSite/BasicWebSite.csproj index b71fbe1efc..fd4b7e9416 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.csproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.csproj @@ -24,5 +24,6 @@ + diff --git a/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj b/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj index 24bd4fa3f8..022617ccd6 100644 --- a/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj +++ b/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj @@ -12,5 +12,6 @@ + diff --git a/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj b/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj index cfda2f9c35..212a619b2e 100644 --- a/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj +++ b/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj @@ -10,5 +10,6 @@ + diff --git a/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj b/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj index ecf6d47b77..c2307238bc 100644 --- a/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj +++ b/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj @@ -25,6 +25,7 @@ --> + diff --git a/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj b/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj index 3d83f2342f..a0930364cb 100644 --- a/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj +++ b/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj @@ -12,5 +12,6 @@ + diff --git a/test/WebSites/RazorWebSite/RazorWebSite.csproj b/test/WebSites/RazorWebSite/RazorWebSite.csproj index 4c8140635e..9de4814189 100644 --- a/test/WebSites/RazorWebSite/RazorWebSite.csproj +++ b/test/WebSites/RazorWebSite/RazorWebSite.csproj @@ -17,5 +17,6 @@ + diff --git a/test/WebSites/SecurityWebSite/SecurityWebSite.csproj b/test/WebSites/SecurityWebSite/SecurityWebSite.csproj index 5486e54caf..86d439be9f 100644 --- a/test/WebSites/SecurityWebSite/SecurityWebSite.csproj +++ b/test/WebSites/SecurityWebSite/SecurityWebSite.csproj @@ -11,5 +11,6 @@ + diff --git a/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj b/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj index 991903c25b..cf4976e941 100644 --- a/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj +++ b/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj @@ -11,5 +11,6 @@ + From 5c8dfef15ee6bc378c6cfc5466ba244473ed5631 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 9 Sep 2018 21:48:23 -0700 Subject: [PATCH 252/316] Change `CollectionModelBinder` and `ComplexTypeModelBinder` to enforce `[BindRequired]` - #8180 - add an error when binding fails for top-level model - same case as when MVC creates "default" / empty model i.e. `ParameterBinder` can't detect this - update `CollectionModelBinder` subclasses and the various providers as well - controlled by existing `MvcOptions.AllowValidatingTopLevelNodes` option smaller issue: - change `ModelBinding_MissingBindRequiredMember` resource to mention parameters too --- .../Metadata/ModelBindingMessageProvider.cs | 4 +- .../ModelBinding/Binders/ArrayModelBinder.cs | 24 +++ .../Binders/ArrayModelBinderProvider.cs | 8 +- .../Binders/CollectionModelBinder.cs | 59 ++++++ .../Binders/CollectionModelBinderProvider.cs | 16 +- .../Binders/ComplexTypeModelBinder.cs | 51 ++++- .../Binders/ComplexTypeModelBinderProvider.cs | 7 +- .../Binders/DictionaryModelBinder.cs | 48 +++++ .../Binders/DictionaryModelBinderProvider.cs | 9 +- .../Properties/Resources.Designer.cs | 4 +- .../Resources.resx | 56 ++--- .../Binders/ArrayModelBinderProviderTest.cs | 25 +++ .../Binders/ArrayModelBinderTest.cs | 108 ++++++++-- .../CollectionModelBinderProviderTest.cs | 25 +++ .../Binders/CollectionModelBinderTest.cs | 109 ++++++++-- .../ComplexTypeModelBinderProviderTest.cs | 32 +++ .../Binders/ComplexTypeModelBinderTest.cs | 199 +++++++++++++++++- .../DictionaryModelBinderProviderTest.cs | 33 +++ .../Binders/DictionaryModelBinderTest.cs | 110 ++++++++-- .../TestModelBinderProviderContext.cs | 14 +- .../InputValidationTests.cs | 8 +- .../CollectionModelBinderIntegrationTest.cs | 4 +- .../ComplexTypeModelBinderIntegrationTest.cs | 16 +- 23 files changed, 843 insertions(+), 126 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs index 5ab37cfd8b..c91bfe4cb0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs @@ -14,7 +14,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// Error message the model binding system adds when a property with an associated /// BindRequiredAttribute is not bound. ///
- /// Default is "A value for the '{0}' property was not provided.". + /// + /// Default is "A value for the '{0}' parameter or property was not provided.". + /// public virtual Func MissingBindRequiredValueAccessor { get; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs index 96c6a31288..c884d8f685 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs @@ -38,11 +38,35 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders /// The for binding . /// /// The . + /// + /// The binder will not add an error for an unbound top-level model even if + /// is . + /// public ArrayModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) : base(elementBinder, loggerFactory) { } + /// + /// Creates a new . + /// + /// + /// The for binding . + /// + /// The . + /// + /// Indication that validation of top-level models is enabled. If and + /// is for a top-level model, the binder + /// adds a error when the model is not bound. + /// + public ArrayModelBinder( + IModelBinder elementBinder, + ILoggerFactory loggerFactory, + bool allowValidatingTopLevelNodes) + : base(elementBinder, loggerFactory, allowValidatingTopLevelNodes) + { + } + /// public override bool CanCreateInstance(Type targetType) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs index 190390b9c7..c08263056e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs @@ -4,6 +4,7 @@ using System; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { @@ -27,7 +28,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var binderType = typeof(ArrayModelBinder<>).MakeGenericType(elementType); var loggerFactory = context.Services.GetRequiredService(); - return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + var mvcOptions = context.Services.GetRequiredService>().Value; + return (IModelBinder)Activator.CreateInstance( + binderType, + elementBinder, + loggerFactory, + mvcOptions.AllowValidatingTopLevelNodes); } return null; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs index 16834ebadc..372a7da770 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs @@ -44,7 +44,29 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders /// /// The for binding elements. /// The . + /// + /// The binder will not add an error for an unbound top-level model even if + /// is . + /// public CollectionModelBinder(IModelBinder elementBinder, ILoggerFactory loggerFactory) + : this(elementBinder, loggerFactory, allowValidatingTopLevelNodes: false) + { + } + + /// + /// Creates a new . + /// + /// The for binding elements. + /// The . + /// + /// Indication that validation of top-level models is enabled. If and + /// is for a top-level model, the binder + /// adds a error when the model is not bound. + /// + public CollectionModelBinder( + IModelBinder elementBinder, + ILoggerFactory loggerFactory, + bool allowValidatingTopLevelNodes) { if (elementBinder == null) { @@ -58,8 +80,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders ElementBinder = elementBinder; Logger = loggerFactory.CreateLogger(GetType()); + AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; } + // Internal for testing. + internal bool AllowValidatingTopLevelNodes { get; } + /// /// Gets the instances for binding collection elements. /// @@ -94,6 +120,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders model = CreateEmptyCollection(bindingContext.ModelType); } + if (AllowValidatingTopLevelNodes) + { + AddErrorIfBindingRequired(bindingContext); + } + bindingContext.Result = ModelBindingResult.Success(model); } @@ -161,6 +192,34 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders typeof(ICollection).IsAssignableFrom(targetType); } + /// + /// Add a to if + /// . + /// + /// The . + /// + /// + /// This method should be called only when is + /// and a top-level model was not bound. + /// + /// + /// For back-compatibility reasons, must have + /// equal to when a + /// top-level model is not bound. Therefore, ParameterBinder can not detect a + /// failure for collections. Add the error here. + /// + /// + protected void AddErrorIfBindingRequired(ModelBindingContext bindingContext) + { + var modelMetadata = bindingContext.ModelMetadata; + if (modelMetadata.IsBindingRequired) + { + var messageProvider = modelMetadata.ModelBindingMessageProvider; + var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName); + bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message); + } + } + /// /// Create an assignable to . /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs index bacc1f371f..5781289596 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs @@ -7,6 +7,7 @@ using System.Reflection; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { @@ -32,7 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } var loggerFactory = context.Services.GetRequiredService(); - + var mvcOptions = context.Services.GetRequiredService>().Value; + // If the model type is ICollection<> then we can call its Add method, so we can always support it. var collectionType = ClosedGenericMatcher.ExtractGenericInterface(modelType, typeof(ICollection<>)); if (collectionType != null) @@ -41,7 +43,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); var binderType = typeof(CollectionModelBinder<>).MakeGenericType(collectionType.GenericTypeArguments); - return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + return (IModelBinder)Activator.CreateInstance( + binderType, + elementBinder, + loggerFactory, + mvcOptions.AllowValidatingTopLevelNodes); } // If the model type is IEnumerable<> then we need to know if we can assign a List<> to it, since @@ -57,7 +63,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var elementBinder = context.CreateBinder(context.MetadataProvider.GetMetadataForType(elementType)); var binderType = typeof(CollectionModelBinder<>).MakeGenericType(enumerableType.GenericTypeArguments); - return (IModelBinder)Activator.CreateInstance(binderType, elementBinder, loggerFactory); + return (IModelBinder)Activator.CreateInstance( + binderType, + elementBinder, + loggerFactory, + mvcOptions.AllowValidatingTopLevelNodes); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs index 2ab1cdb70c..5c1e82a80c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs @@ -45,9 +45,33 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders /// The of binders to use for binding properties. /// /// The . + /// + /// The binder will not add an error for an unbound top-level model even if + /// is . + /// public ComplexTypeModelBinder( IDictionary propertyBinders, ILoggerFactory loggerFactory) + : this(propertyBinders, loggerFactory, allowValidatingTopLevelNodes: false) + { + } + + /// + /// Creates a new . + /// + /// + /// The of binders to use for binding properties. + /// + /// The . + /// + /// Indication that validation of top-level models is enabled. If and + /// is for a top-level model, the binder + /// adds a error when the model is not bound. + /// + public ComplexTypeModelBinder( + IDictionary propertyBinders, + ILoggerFactory loggerFactory, + bool allowValidatingTopLevelNodes) { if (propertyBinders == null) { @@ -61,8 +85,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders _propertyBinders = propertyBinders; _logger = loggerFactory.CreateLogger(); + AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; } + // Internal for testing. + internal bool AllowValidatingTopLevelNodes { get; } + /// public Task BindModelAsync(ModelBindingContext bindingContext) { @@ -91,9 +119,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders bindingContext.Model = CreateModel(bindingContext); } - for (var i = 0; i < bindingContext.ModelMetadata.Properties.Count; i++) + var modelMetadata = bindingContext.ModelMetadata; + var attemptedPropertyBinding = false; + for (var i = 0; i < modelMetadata.Properties.Count; i++) { - var property = bindingContext.ModelMetadata.Properties[i]; + var property = modelMetadata.Properties[i]; if (!CanBindProperty(bindingContext, property)) { continue; @@ -127,15 +157,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders if (result.IsModelSet) { + attemptedPropertyBinding = true; SetProperty(bindingContext, modelName, property, result); } else if (property.IsBindingRequired) { + attemptedPropertyBinding = true; var message = property.ModelBindingMessageProvider.MissingBindRequiredValueAccessor(fieldName); bindingContext.ModelState.TryAddModelError(modelName, message); } } + // Have we created a top-level model despite an inability to bind anything in said model and a lack of + // other IsBindingRequired errors? Does that violate [BindRequired] on the model? This case occurs when + // 1. The top-level model has no public settable properties. + // 2. All properties in a [BindRequired] model have [BindNever] or are otherwise excluded from binding. + // 3. No data exists for any property. + if (AllowValidatingTopLevelNodes && + !attemptedPropertyBinding && + bindingContext.IsTopLevelObject && + modelMetadata.IsBindingRequired) + { + var messageProvider = modelMetadata.ModelBindingMessageProvider; + var message = messageProvider.MissingBindRequiredValueAccessor(bindingContext.FieldName); + bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, message); + } + bindingContext.Result = ModelBindingResult.Success(bindingContext.Model); _logger.DoneAttemptingToBindModel(bindingContext); } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs index f0d6b5989c..ac8d2f13d3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { @@ -31,7 +32,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } var loggerFactory = context.Services.GetRequiredService(); - return new ComplexTypeModelBinder(propertyBinders, loggerFactory); + var mvcOptions = context.Services.GetRequiredService>().Value; + return new ComplexTypeModelBinder( + propertyBinders, + loggerFactory, + mvcOptions.AllowValidatingTopLevelNodes); } return null; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs index fbb0c53db1..0954f87e6c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs @@ -43,6 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders /// The for . /// The for . /// The . + /// + /// The binder will not add an error for an unbound top-level model even if + /// is . + /// public DictionaryModelBinder(IModelBinder keyBinder, IModelBinder valueBinder, ILoggerFactory loggerFactory) : base(new KeyValuePairModelBinder(keyBinder, valueBinder, loggerFactory), loggerFactory) { @@ -54,6 +58,40 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders _valueBinder = valueBinder; } + /// + /// Creates a new . + /// + /// The for . + /// The for . + /// The . + /// + /// Indication that validation of top-level models is enabled. If and + /// is for a top-level model, the binder + /// adds a error when the model is not bound. + /// + public DictionaryModelBinder( + IModelBinder keyBinder, + IModelBinder valueBinder, + ILoggerFactory loggerFactory, + bool allowValidatingTopLevelNodes) + : base( + new KeyValuePairModelBinder(keyBinder, valueBinder, loggerFactory), + loggerFactory, + // CollectionModelBinder should not check IsRequired, done in this model binder. + allowValidatingTopLevelNodes: false) + { + if (valueBinder == null) + { + throw new ArgumentNullException(nameof(valueBinder)); + } + + _valueBinder = valueBinder; + AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; + } + + // Internal for testing. + internal new bool AllowValidatingTopLevelNodes { get; } + /// public override async Task BindModelAsync(ModelBindingContext bindingContext) { @@ -85,6 +123,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { // No IEnumerableValueProvider available for the fallback approach. For example the user may have // replaced the ValueProvider with something other than a CompositeValueProvider. + if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject) + { + AddErrorIfBindingRequired(bindingContext); + } + return; } @@ -94,6 +137,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders if (keys.Count == 0) { // No entries with the expected keys. + if (AllowValidatingTopLevelNodes && bindingContext.IsTopLevelObject) + { + AddErrorIfBindingRequired(bindingContext); + } + return; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs index 0d7db7c064..4ef0c83725 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { @@ -34,7 +35,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var binderType = typeof(DictionaryModelBinder<,>).MakeGenericType(dictionaryType.GenericTypeArguments); var loggerFactory = context.Services.GetRequiredService(); - return (IModelBinder)Activator.CreateInstance(binderType, keyBinder, valueBinder, loggerFactory); + var mvcOptions = context.Services.GetRequiredService>().Value; + return (IModelBinder)Activator.CreateInstance( + binderType, + keyBinder, + valueBinder, + loggerFactory, + mvcOptions.AllowValidatingTopLevelNodes); } return null; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index 70d895c8af..c00c53bcf8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -795,7 +795,7 @@ namespace Microsoft.AspNetCore.Mvc.Core => GetString("ModelBinderUtil_ModelMetadataCannotBeNull"); /// - /// A value for the '{0}' property was not provided. + /// A value for the '{0}' parameter or property was not provided. /// internal static string ModelBinding_MissingBindRequiredMember { @@ -803,7 +803,7 @@ namespace Microsoft.AspNetCore.Mvc.Core } /// - /// A value for the '{0}' property was not provided. + /// A value for the '{0}' parameter or property was not provided. /// internal static string FormatModelBinding_MissingBindRequiredMember(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("ModelBinding_MissingBindRequiredMember"), p0); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 5ec7fea828..30faf9ec02 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -1,17 +1,17 @@  - @@ -295,7 +295,7 @@ The binding context cannot have a null ModelMetadata. - A value for the '{0}' property was not provided. + A value for the '{0}' parameter or property was not provided. A non-empty request body is required. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs index 83a96daa9a..537dd3bf4e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs @@ -51,6 +51,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.IsType(typeof(ArrayModelBinder<>).MakeGenericType(modelType.GetElementType()), result); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Create_ForArrayType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes( + bool allowValidatingTopLevelNodes) + { + // Arrange + var provider = new ArrayModelBinderProvider(); + + var context = new TestModelBinderProviderContext(typeof(int[])); + context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; + context.OnCreatingBinder(m => + { + Assert.Equal(typeof(int), m.ModelType); + return Mock.Of(); + }); + + // Act + var result = provider.GetBinder(context); + + // Assert + var binder = Assert.IsType>(result); + Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes); + } + [Fact] public void Create_ForModelMetadataReadOnly_ReturnsNull() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs index 8c3a372f06..d3efdd8775 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding.Internal; @@ -42,13 +43,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Equal(new[] { 42, 84 }, array); } - [Fact] - public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject() + private IActionResult ActionWithArrayParameter(string[] parameter) => null; + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public async Task ArrayModelBinder_CreatesEmptyCollection_IfIsTopLevelObject( + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var binder = new ArrayModelBinder( new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.IsTopLevelObject = true; @@ -57,7 +66,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders bindingContext.ModelName = "modelName"; var metadataProvider = new TestModelMetadataProvider(); - bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(string[])); + var parameter = typeof(ArrayModelBinderTest) + .GetMethod(nameof(ActionWithArrayParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); @@ -67,22 +82,74 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.Empty(Assert.IsType(bindingContext.Result.Model)); Assert.True(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); } - [Theory] - [InlineData("")] - [InlineData("param")] - public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) + [Fact] + public async Task ArrayModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject() { // Arrange var binder = new ArrayModelBinder( new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + var bindingContext = CreateContext(); + bindingContext.IsTopLevelObject = true; + bindingContext.FieldName = "fieldName"; + bindingContext.ModelName = "modelName"; + + var metadataProvider = new TestModelMetadataProvider(); + var parameter = typeof(ArrayModelBinderTest) + .GetMethod(nameof(ActionWithArrayParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); + + bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.Empty(Assert.IsType(bindingContext.Result.Model)); + Assert.True(bindingContext.Result.IsModelSet); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal("modelName", keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); + } + + [Theory] + [InlineData("", false, false)] + [InlineData("", true, false)] + [InlineData("", false, true)] + [InlineData("", true, true)] + [InlineData("param", false, false)] + [InlineData("param", true, false)] + [InlineData("param", false, true)] + [InlineData("param", true, true)] + public async Task ArrayModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject( + string prefix, + bool allowValidatingTopLevelNodes, + bool isBindingRequired) + { + // Arrange + var binder = new ArrayModelBinder( + new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ArrayProperty"); var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForProperty(typeof(ModelWithArrayProperty), nameof(ModelWithArrayProperty.ArrayProperty)) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( typeof(ModelWithArrayProperty), nameof(ModelWithArrayProperty.ArrayProperty)); @@ -94,6 +161,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.False(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); } public static TheoryData ArrayModelData @@ -177,23 +245,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private static DefaultModelBindingContext GetBindingContext(IValueProvider valueProvider) { - var bindingContext = new DefaultModelBindingContext() - { - ModelName = "someName", - ModelState = new ModelStateDictionary(), - ValueProvider = valueProvider, - }; + var bindingContext = CreateContext(); + bindingContext.ModelName = "someName"; + bindingContext.ValueProvider = valueProvider; + return bindingContext; } private static DefaultModelBindingContext CreateContext() { - var modelBindingContext = new DefaultModelBindingContext() + var actionContext = new ActionContext { - ActionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext(), - }, + HttpContext = new DefaultHttpContext(), + }; + var modelBindingContext = new DefaultModelBindingContext + { + ActionContext = actionContext, + ModelState = actionContext.ModelState, }; return modelBindingContext; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs index d0cd6091bb..d3ee57f216 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs @@ -66,6 +66,31 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.IsType>(result); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes( + bool allowValidatingTopLevelNodes) + { + // Arrange + var provider = new CollectionModelBinderProvider(); + + var context = new TestModelBinderProviderContext(typeof(List)); + context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; + context.OnCreatingBinder(m => + { + Assert.Equal(typeof(int), m.ModelType); + return Mock.Of(); + }); + + // Act + var result = provider.GetBinder(context); + + // Assert + var binder = Assert.IsType>(result); + Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes); + } + private class Person { public string Name { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs index b34cd09da5..bc9cdf1981 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Internal; @@ -211,13 +212,21 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Empty(boundCollection.Model); } - [Fact] - public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject() + private IActionResult ActionWithListParameter(List parameter) => null; + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public async Task CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject( + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var binder = new CollectionModelBinder( new StubModelBinder(result: ModelBindingResult.Failed()), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.IsTopLevelObject = true; @@ -226,7 +235,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders bindingContext.ModelName = "modelName"; var metadataProvider = new TestModelMetadataProvider(); - bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(List)); + var parameter = typeof(CollectionModelBinderTest) + .GetMethod(nameof(ActionWithListParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); @@ -236,6 +251,45 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.Empty(Assert.IsType>(bindingContext.Result.Model)); Assert.True(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); + } + + [Fact] + public async Task CollectionModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject() + { + // Arrange + var binder = new CollectionModelBinder( + new StubModelBinder(result: ModelBindingResult.Failed()), + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + var bindingContext = CreateContext(); + bindingContext.IsTopLevelObject = true; + bindingContext.FieldName = "fieldName"; + bindingContext.ModelName = "modelName"; + + var metadataProvider = new TestModelMetadataProvider(); + var parameter = typeof(CollectionModelBinderTest) + .GetMethod(nameof(ActionWithListParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); + + bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.Empty(Assert.IsType>(bindingContext.Result.Model)); + Assert.True(bindingContext.Result.IsModelSet); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal("modelName", keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); } // Setup like CollectionModelBinder_CreatesEmptyCollection_IfIsTopLevelObject except @@ -272,19 +326,32 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders } [Theory] - [InlineData("")] - [InlineData("param")] - public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) + [InlineData("", false, false)] + [InlineData("", true, false)] + [InlineData("", false, true)] + [InlineData("", true, true)] + [InlineData("param", false, false)] + [InlineData("param", true, false)] + [InlineData("param", false, true)] + [InlineData("param", true, true)] + public async Task CollectionModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject( + string prefix, + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var binder = new CollectionModelBinder( new StubModelBinder(result: ModelBindingResult.Failed()), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty"); var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForProperty(typeof(ModelWithListProperty), nameof(ModelWithListProperty.ListProperty)) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( typeof(ModelWithListProperty), nameof(ModelWithListProperty.ListProperty)); @@ -296,6 +363,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.False(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); } // Model type -> can create instance. @@ -365,15 +433,11 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders typeof(ModelWithIListProperty), nameof(ModelWithIListProperty.ListProperty)); - var bindingContext = new DefaultModelBindingContext - { - ModelMetadata = metadata, - ModelName = "someName", - ModelState = new ModelStateDictionary(), - ValueProvider = valueProvider, - ValidationState = new ValidationStateDictionary(), - FieldName = "testfieldname", - }; + var bindingContext = CreateContext(); + bindingContext.FieldName = "testfieldname"; + bindingContext.ModelName = "someName"; + bindingContext.ModelMetadata = metadata; + bindingContext.ValueProvider = valueProvider; return bindingContext; } @@ -412,12 +476,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private static DefaultModelBindingContext CreateContext() { + var actionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext(), + }; var modelBindingContext = new DefaultModelBindingContext() { - ActionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext(), - }, + ActionContext = actionContext, + ModelState = actionContext.ModelState, + ValidationState = new ValidationStateDictionary(), }; return modelBindingContext; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs index 9dc044f814..45e27d4ddc 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs @@ -55,6 +55,38 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.IsType(result); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Create_ForSupportedType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes( + bool allowValidatingTopLevelNodes) + { + // Arrange + var provider = new ComplexTypeModelBinderProvider(); + + var context = new TestModelBinderProviderContext(typeof(Person)); + context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; + context.OnCreatingBinder(m => + { + if (m.ModelType == typeof(int) || m.ModelType == typeof(string)) + { + return Mock.Of(); + } + else + { + Assert.False(true, "Not the right model type"); + return null; + } + }); + + // Act + var result = provider.GetBinder(context); + + // Assert + var binder = Assert.IsType(result); + Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes); + } + private class Person { public string Name { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs index 7eaedfa106..32314d7306 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.DataAnnotations; using System.Linq; +using System.Reflection; using System.Runtime.Serialization; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; @@ -275,8 +276,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Equal(expectedCanCreate, canCreate); } - [Fact] - public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject() + private IActionResult ActionWithComplexParameter(Person parameter) => null; + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public async Task BindModelAsync_CreatesModel_IfIsTopLevelObject( + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var mockValueProvider = new Mock(); @@ -287,6 +295,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Mock binder fails to bind all properties. var mockBinder = new StubModelBinder(); + var parameter = typeof(ComplexTypeModelBinderTest) + .GetMethod(nameof(ActionWithComplexParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); + var metadata = metadataProvider.GetMetadataForParameter(parameter); var bindingContext = new DefaultModelBindingContext { IsTopLevelObject = true, @@ -298,7 +314,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var model = new Person(); - var testableBinder = new Mock { CallBase = true }; + var testableBinder = new Mock(allowValidatingTopLevelNodes) + { + CallBase = true + }; testableBinder .Setup(o => o.CreateModelPublic(bindingContext)) .Returns(model) @@ -312,11 +331,149 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.True(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); + var returnedPerson = Assert.IsType(bindingContext.Result.Model); Assert.Same(model, returnedPerson); testableBinder.Verify(); } + [Fact] + public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithNoData() + { + // Arrange + var parameter = typeof(ComplexTypeModelBinderTest) + .GetMethod(nameof(ActionWithComplexParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + var metadata = metadataProvider.GetMetadataForParameter(parameter); + var bindingContext = new DefaultModelBindingContext + { + IsTopLevelObject = true, + FieldName = "fieldName", + ModelMetadata = metadata, + ModelName = string.Empty, + ValueProvider = new TestValueProvider(new Dictionary()), + ModelState = new ModelStateDictionary(), + }; + + // Mock binder fails to bind all properties. + var innerBinder = new StubModelBinder(); + var binders = new Dictionary(); + foreach (var property in metadataProvider.GetMetadataForProperties(typeof(Person))) + { + binders.Add(property, innerBinder); + } + + var binder = new ComplexTypeModelBinder( + binders, + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + Assert.IsType(bindingContext.Result.Model); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal(string.Empty, keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); + } + + private IActionResult ActionWithNoSettablePropertiesParameter(PersonWithNoProperties parameter) => null; + + [Fact] + public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithNoSettableProperties() + { + // Arrange + var parameter = typeof(ComplexTypeModelBinderTest) + .GetMethod( + nameof(ActionWithNoSettablePropertiesParameter), + BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + var metadata = metadataProvider.GetMetadataForParameter(parameter); + var bindingContext = new DefaultModelBindingContext + { + IsTopLevelObject = true, + FieldName = "fieldName", + ModelMetadata = metadata, + ModelName = string.Empty, + ValueProvider = new TestValueProvider(new Dictionary()), + ModelState = new ModelStateDictionary(), + }; + + var binder = new ComplexTypeModelBinder( + new Dictionary(), + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + Assert.IsType(bindingContext.Result.Model); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal(string.Empty, keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); + } + + private IActionResult ActionWithAllPropertiesExcludedParameter(PersonWithAllPropertiesExcluded parameter) => null; + + [Fact] + public async Task BindModelAsync_CreatesModelAndAddsError_IfIsTopLevelObject_WithAllPropertiesExcluded() + { + // Arrange + var parameter = typeof(ComplexTypeModelBinderTest) + .GetMethod( + nameof(ActionWithAllPropertiesExcludedParameter), + BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + var metadata = metadataProvider.GetMetadataForParameter(parameter); + var bindingContext = new DefaultModelBindingContext + { + IsTopLevelObject = true, + FieldName = "fieldName", + ModelMetadata = metadata, + ModelName = string.Empty, + ValueProvider = new TestValueProvider(new Dictionary()), + ModelState = new ModelStateDictionary(), + }; + + var binder = new ComplexTypeModelBinder( + new Dictionary(), + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.True(bindingContext.Result.IsModelSet); + Assert.IsType(bindingContext.Result.Model); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal(string.Empty, keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); + } + [Theory] [InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyInt), false)] // read-only value type [InlineData(nameof(MyModelTestingCanUpdateProperty.ReadOnlyObject), true)] @@ -644,7 +801,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var modelError = Assert.Single(entry.Errors); Assert.Null(modelError.Exception); Assert.NotNull(modelError.ErrorMessage); - Assert.Equal("A value for the 'Age' property was not provided.", modelError.ErrorMessage); + Assert.Equal("A value for the 'Age' parameter or property was not provided.", modelError.ErrorMessage); } [Fact] @@ -678,7 +835,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders var modelError = Assert.Single(entry.Errors); Assert.Null(modelError.Exception); Assert.NotNull(modelError.ErrorMessage); - Assert.Equal("A value for the 'Age' property was not provided.", modelError.ErrorMessage); + Assert.Equal("A value for the 'Age' parameter or property was not provided.", modelError.ErrorMessage); } [Fact] @@ -1203,6 +1360,23 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders public string name = null; } + private class PersonWithAllPropertiesExcluded + { + [BindNever] + public DateTime DateOfBirth { get; set; } + + [BindNever] + public DateTime? DateOfDeath { get; set; } + + [BindNever] + public string FirstName { get; set; } + + [BindNever] + public string LastName { get; set; } + + public string NonUpdateableProperty { get; private set; } + } + private class PersonWithBindExclusion { [BindNever] @@ -1405,13 +1579,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders { } + public TestableComplexTypeModelBinder(bool allowValidatingTopLevelNodes) + : this(new Dictionary(), allowValidatingTopLevelNodes) + { + } + public TestableComplexTypeModelBinder(IDictionary propertyBinders) : base(propertyBinders, NullLoggerFactory.Instance) { - Results = new Dictionary(); } - public Dictionary Results { get; } + public TestableComplexTypeModelBinder( + IDictionary propertyBinders, + bool allowValidatingTopLevelNodes) + : base(propertyBinders, NullLoggerFactory.Instance, allowValidatingTopLevelNodes) + { + } + + public Dictionary Results { get; } = new Dictionary(); public virtual Task BindPropertyPublic(ModelBindingContext bindingContext) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs index d60c3b2628..6d7add41c7 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs @@ -60,6 +60,39 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.IsType>(result); } + [Theory] + [InlineData(false)] + [InlineData(true)] + public void Create_ForDictionaryType_ReturnsBinder_WithExpectedAllowValidatingTopLevelNodes( + bool allowValidatingTopLevelNodes) + { + // Arrange + var provider = new DictionaryModelBinderProvider(); + + var context = new TestModelBinderProviderContext(typeof(Dictionary)); + context.MvcOptions.AllowValidatingTopLevelNodes = allowValidatingTopLevelNodes; + context.OnCreatingBinder(m => + { + if (m.ModelType == typeof(KeyValuePair) || m.ModelType == typeof(string)) + { + return Mock.Of(); + } + else + { + Assert.False(true, "Not the right model type"); + return null; + } + }); + + // Act + var result = provider.GetBinder(context); + + // Assert + var binder = Assert.IsType>(result); + Assert.Equal(allowValidatingTopLevelNodes, binder.AllowValidatingTopLevelNodes); + Assert.False(((CollectionModelBinder>)binder).AllowValidatingTopLevelNodes); + } + private class Person { public string Name { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs index 2beb2a33f9..db059c0090 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Internal; @@ -343,14 +344,22 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders Assert.Equal(expectedDictionary, resultDictionary); } - [Fact] - public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject() + private IActionResult ActionWithDictionaryParameter(Dictionary parameter) => null; + + [Theory] + [InlineData(false, false)] + [InlineData(false, true)] + [InlineData(true, false)] + public async Task DictionaryModelBinder_CreatesEmptyCollection_IfIsTopLevelObject( + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var binder = new DictionaryModelBinder( new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.IsTopLevelObject = true; @@ -359,7 +368,13 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders bindingContext.ModelName = "modelName"; var metadataProvider = new TestModelMetadataProvider(); - bindingContext.ModelMetadata = metadataProvider.GetMetadataForType(typeof(Dictionary)); + var parameter = typeof(DictionaryModelBinderTest) + .GetMethod(nameof(ActionWithDictionaryParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); @@ -369,23 +384,78 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.Empty(Assert.IsType>(bindingContext.Result.Model)); Assert.True(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); + } + + [Fact] + public async Task DictionaryModelBinder_CreatesEmptyCollectionAndAddsError_IfIsTopLevelObject() + { + // Arrange + var binder = new DictionaryModelBinder( + new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), + new SimpleTypeModelBinder(typeof(string), NullLoggerFactory.Instance), + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes: true); + + var bindingContext = CreateContext(); + bindingContext.IsTopLevelObject = true; + bindingContext.FieldName = "fieldName"; + bindingContext.ModelName = "modelName"; + + var metadataProvider = new TestModelMetadataProvider(); + var parameter = typeof(DictionaryModelBinderTest) + .GetMethod(nameof(ActionWithDictionaryParameter), BindingFlags.Instance | BindingFlags.NonPublic) + .GetParameters()[0]; + metadataProvider + .ForParameter(parameter) + .BindingDetails(b => b.IsBindingRequired = true); + bindingContext.ModelMetadata = metadataProvider.GetMetadataForParameter(parameter); + + bindingContext.ValueProvider = new TestValueProvider(new Dictionary()); + + // Act + await binder.BindModelAsync(bindingContext); + + // Assert + Assert.Empty(Assert.IsType>(bindingContext.Result.Model)); + Assert.True(bindingContext.Result.IsModelSet); + + var keyValuePair = Assert.Single(bindingContext.ModelState); + Assert.Equal("modelName", keyValuePair.Key); + var error = Assert.Single(keyValuePair.Value.Errors); + Assert.Equal("A value for the 'fieldName' parameter or property was not provided.", error.ErrorMessage); } [Theory] - [InlineData("")] - [InlineData("param")] - public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject(string prefix) + [InlineData("", false, false)] + [InlineData("", true, false)] + [InlineData("", false, true)] + [InlineData("", true, true)] + [InlineData("param", false, false)] + [InlineData("param", true, false)] + [InlineData("param", false, true)] + [InlineData("param", true, true)] + public async Task DictionaryModelBinder_DoesNotCreateCollection_IfNotIsTopLevelObject( + string prefix, + bool allowValidatingTopLevelNodes, + bool isBindingRequired) { // Arrange var binder = new DictionaryModelBinder( new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance), new SimpleTypeModelBinder(typeof(int), NullLoggerFactory.Instance), - NullLoggerFactory.Instance); + NullLoggerFactory.Instance, + allowValidatingTopLevelNodes); var bindingContext = CreateContext(); bindingContext.ModelName = ModelNames.CreatePropertyModelName(prefix, "ListProperty"); var metadataProvider = new TestModelMetadataProvider(); + metadataProvider + .ForProperty( + typeof(ModelWithDictionaryProperties), + nameof(ModelWithDictionaryProperties.DictionaryProperty)) + .BindingDetails(b => b.IsBindingRequired = isBindingRequired); bindingContext.ModelMetadata = metadataProvider.GetMetadataForProperty( typeof(ModelWithDictionaryProperties), nameof(ModelWithDictionaryProperties.DictionaryProperty)); @@ -397,6 +467,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders // Assert Assert.False(bindingContext.Result.IsModelSet); + Assert.Equal(0, bindingContext.ModelState.ErrorCount); } // Model type -> can create instance. @@ -436,13 +507,14 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders private static DefaultModelBindingContext CreateContext() { + var actionContext = new ActionContext() + { + HttpContext = new DefaultHttpContext(), + }; var modelBindingContext = new DefaultModelBindingContext() { - ActionContext = new ActionContext() - { - HttpContext = new DefaultHttpContext(), - }, - ModelState = new ModelStateDictionary(), + ActionContext = actionContext, + ModelState = actionContext.ModelState, ValidationState = new ValidationStateDictionary(), }; @@ -495,14 +567,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Binders valueProvider.Add(kvp.Key, string.Empty); } - var bindingContext = new DefaultModelBindingContext - { - ModelMetadata = metadata, - ModelName = "someName", - ModelState = new ModelStateDictionary(), - ValueProvider = valueProvider, - ValidationState = new ValidationStateDictionary(), - }; + var bindingContext = CreateContext(); + bindingContext.ModelMetadata = metadata; + bindingContext.ModelName = "someName"; + bindingContext.ValueProvider = valueProvider; return bindingContext; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs index 514e012c76..d0c29dae8d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs @@ -36,13 +36,16 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding BindingSource = Metadata.BindingSource, PropertyFilterProvider = Metadata.PropertyFilterProvider, }; - Services = GetServices(); + + (Services, MvcOptions) = GetServicesAndOptions(); } public override BindingInfo BindingInfo => _bindingInfo; public override ModelMetadata Metadata { get; } + public MvcOptions MvcOptions { get; } + public override IModelMetadataProvider MetadataProvider { get; } public override IServiceProvider Services { get; } @@ -77,12 +80,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding _binderCreators.Add((m) => m.Equals(metadata) ? binderCreator() : null); } - private static IServiceProvider GetServices() + private static (IServiceProvider, MvcOptions) GetServicesAndOptions() { var services = new ServiceCollection(); services.AddSingleton(); - services.AddSingleton(Options.Create(new MvcOptions())); - return services.BuildServiceProvider(); + + var mvcOptions = new MvcOptions(); + services.AddSingleton(Options.Create(mvcOptions)); + + return (services.BuildServiceProvider(), mvcOptions); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs index 5c5c9e7189..2e87b4b44c 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs @@ -89,10 +89,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "The RequiredProp field is required.", errors["RequiredProp"]); Assert.Equal( - "A value for the 'BindRequiredProp' property was not provided.", + "A value for the 'BindRequiredProp' parameter or property was not provided.", errors["BindRequiredProp"]); Assert.Equal( - "A value for the 'RequiredAndBindRequiredProp' property was not provided.", + "A value for the 'RequiredAndBindRequiredProp' parameter or property was not provided.", errors["RequiredAndBindRequiredProp"]); Assert.Equal( "The field OptionalStringLengthProp must be a string with a maximum length of 5.", @@ -104,10 +104,10 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests "The requiredParam field is required.", errors["requiredParam"]); Assert.Equal( - "A value for the 'bindRequiredParam' property was not provided.", + "A value for the 'bindRequiredParam' parameter or property was not provided.", errors["bindRequiredParam"]); Assert.Equal( - "A value for the 'requiredAndBindRequiredParam' property was not provided.", + "A value for the 'requiredAndBindRequiredParam' parameter or property was not provided.", errors["requiredAndBindRequiredParam"]); Assert.Equal( "The field optionalStringLengthParam must be a string with a maximum length of 5.", diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs index 7cb0c782d2..aec3949a3d 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs @@ -333,13 +333,13 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); var error = Assert.Single(entry.Errors); - Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage); entry = Assert.Single(modelState, kvp => kvp.Key == "parameter[1].Name").Value; Assert.Null(entry.RawValue); Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); error = Assert.Single(entry.Errors); - Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs index 8f4fc6470f..beffb6d9a1 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs @@ -2123,7 +2123,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["Customer"].Errors); - Assert.Equal("A value for the 'Customer' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Customer' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2245,7 +2245,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["parameter.Customer.Name"].Errors); - Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2299,7 +2299,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["Customer.Name"].Errors); - Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2357,7 +2357,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["customParameter.Customer.Name"].Errors); - Assert.Equal("A value for the 'Name' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'Name' parameter or property was not provided.", error.ErrorMessage); } private class Order12 @@ -2411,7 +2411,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["ProductName"].Errors); - Assert.Equal("A value for the 'ProductName' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'ProductName' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2463,7 +2463,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["customParameter.ProductName"].Errors); - Assert.Equal("A value for the 'ProductName' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'ProductName' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2563,7 +2563,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["OrderIds"].Errors); - Assert.Equal("A value for the 'OrderIds' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'OrderIds' parameter or property was not provided.", error.ErrorMessage); } [Fact] @@ -2615,7 +2615,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Null(entry.RawValue); Assert.Null(entry.AttemptedValue); var error = Assert.Single(modelState["customParameter.OrderIds"].Errors); - Assert.Equal("A value for the 'OrderIds' property was not provided.", error.ErrorMessage); + Assert.Equal("A value for the 'OrderIds' parameter or property was not provided.", error.ErrorMessage); } [Fact] From 89a962716fa37a84127864c7913a9f1ee9eda588 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 22 Sep 2018 15:35:55 -0700 Subject: [PATCH 253/316] React to Routing LinkGenerator changes --- build/dependencies.props | 4 ++-- .../Routing/EndpointRoutingUrlHelper.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 22824154bd..97da37f7e6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview3-35252 2.2.0-preview3-35252 2.2.0-preview3-35252 - 2.2.0-a-preview3-outbound-parameter-tranformer-16996 - 2.2.0-a-preview3-outbound-parameter-tranformer-16996 + 2.2.0-a-preview3-ambientvalues-17006 + 2.2.0-a-preview3-ambientvalues-17006 2.2.0-preview3-35252 2.2.0-preview3-35252 2.2.0-preview3-35252 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs index adc94a7d8e..528d6e2efe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing ActionContext.HttpContext, routeName: null, values, - new FragmentString(urlActionContext.Fragment == null ? null : "#" + urlActionContext.Fragment)); + fragment: new FragmentString(urlActionContext.Fragment == null ? null : "#" + urlActionContext.Fragment)); return GenerateUrl(urlActionContext.Protocol, urlActionContext.Host, path); } @@ -101,7 +101,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing ActionContext.HttpContext, routeContext.RouteName, routeContext.Values, - new FragmentString(routeContext.Fragment == null ? null : "#" + routeContext.Fragment)); + fragment: new FragmentString(routeContext.Fragment == null ? null : "#" + routeContext.Fragment)); return GenerateUrl(routeContext.Protocol, routeContext.Host, path); } } From db7555b0bacc023a5a9b55978af464c933e61814 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 23 Sep 2018 19:25:04 +0000 Subject: [PATCH 254/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 97da37f7e6..7d118388d6 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview3-35252 - 2.2.0-preview1-20180911.1 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-a-preview3-ambientvalues-17006 - 2.2.0-a-preview3-ambientvalues-17006 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-preview3-35301 + 2.2.0-preview1-20180918.1 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 5.2.6 2.8.0 2.8.0 - 2.2.0-preview3-35252 + 2.2.0-preview3-35301 1.7.0 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 2.1.0 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 2.0.9 2.1.3 2.2.0-preview2-26905-02 - 2.2.0-preview3-35252 - 2.2.0-preview3-35252 + 2.2.0-preview3-35301 + 2.2.0-preview3-35301 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 7124f37441..649bf2ba0b 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180911.1 -commithash:ddfecdfc6e8e4859db5a0daea578070b862aac65 +version:2.2.0-preview1-20180918.1 +commithash:ad5e3fc53442741a0dd49bce437d2ac72f4b5800 From 50cef4822a5fda5490896818377a15611246f703 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 24 Sep 2018 10:46:54 -0700 Subject: [PATCH 255/316] Invoke FlushAsync before disposing the HttpResponseWriter in JsonResultExecutor Fixes #8486 --- .../Internal/JsonResultExecutor.cs | 8 ++- .../Internal/JsonResultExecutorTest.cs | 62 +++++++++++++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs index f7e2fc09f2..332d1bdc59 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs @@ -86,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal /// The . /// The . /// A which will complete when writing has completed. - public virtual Task ExecuteAsync(ActionContext context, JsonResult result) + public virtual async Task ExecuteAsync(ActionContext context, JsonResult result) { if (context == null) { @@ -128,9 +128,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal var jsonSerializer = JsonSerializer.Create(serializerSettings); jsonSerializer.Serialize(jsonWriter, result.Value); } - } - return Task.CompletedTask; + // Perf: call FlushAsync to call WriteAsync on the stream with any content left in the TextWriter's + // buffers. This is better than just letting dispose handle it (which would result in a synchronous write). + await writer.FlushAsync(); + } } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs index 2dbbeb1dad..8803474ccd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs @@ -5,6 +5,7 @@ using System; using System.Buffers; using System.IO; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; @@ -14,6 +15,7 @@ using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Microsoft.Net.Http.Headers; +using Moq; using Newtonsoft.Json; using Xunit; @@ -217,6 +219,66 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Json.Internal Assert.Equal(expected, logger.MostRecentMessage); } + [Fact] + public async Task ExecuteAsync_WritesToTheResponseStream_WhenContentIsLargerThanBuffer() + { + // Arrange + var writeLength = 2 * TestHttpResponseStreamWriterFactory.DefaultBufferSize + 4; + var text = new string('a', writeLength); + var expectedWriteCallCount = Math.Ceiling((double)writeLength / TestHttpResponseStreamWriterFactory.DefaultBufferSize); + + var stream = new Mock(); + stream.SetupGet(s => s.CanWrite).Returns(true); + var httpContext = new DefaultHttpContext(); + httpContext.Response.Body = stream.Object; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + var result = new JsonResult(text); + var executor = CreateExecutor(); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + // HttpResponseStreamWriter buffers content up to the buffer size (16k). When writes exceed the buffer size, it'll perform a synchronous + // write to the response stream. + stream.Verify(s => s.Write(It.IsAny(), It.IsAny(), TestHttpResponseStreamWriterFactory.DefaultBufferSize), Times.Exactly(2)); + + // Remainder buffered content is written asynchronously as part of the FlushAsync. + stream.Verify(s => s.WriteAsync(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny()), Times.Once()); + + // Dispose does not call Flush + stream.Verify(s => s.Flush(), Times.Never()); + } + + [Theory] + [InlineData(5)] + [InlineData(TestHttpResponseStreamWriterFactory.DefaultBufferSize - 30)] + public async Task ExecuteAsync_DoesNotWriteSynchronouslyToTheResponseBody_WhenContentIsSmallerThanBufferSize(int writeLength) + { + // Arrange + var text = new string('a', writeLength); + + var stream = new Mock(); + stream.SetupGet(s => s.CanWrite).Returns(true); + var httpContext = new DefaultHttpContext(); + httpContext.Response.Body = stream.Object; + var actionContext = new ActionContext(httpContext, new RouteData(), new ActionDescriptor()); + + var result = new JsonResult(text); + var executor = CreateExecutor(); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + // HttpResponseStreamWriter buffers content up to the buffer size (16k) and will asynchronously write content to the response as part + // of the FlushAsync call if the content written to it is smaller than the buffer size. + // This test verifies that no synchronous writes are performed in this scenario. + stream.Verify(s => s.Flush(), Times.Never()); + stream.Verify(s => s.Write(It.IsAny(), It.IsAny(), It.IsAny()), Times.Never()); + } + private static JsonResultExecutor CreateExecutor(ILogger logger = null) { return new JsonResultExecutor( From 831937c86c897da146edfee8ba7f7790aa5cb991 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Mon, 24 Sep 2018 21:08:28 -0700 Subject: [PATCH 256/316] Add LinkGenerator extensions for MVC --- .../ControllerLinkGeneratorExtensions.cs | 281 ++++++++++++++++++ .../Routing/PageLinkGeneratorExtensions.cs | 268 +++++++++++++++++ .../Routing/UrlHelper.cs | 26 +- .../Routing/UrlHelperBase.cs | 107 +++++++ .../UrlHelperExtensions.cs | 45 +-- .../ControllerLinkGeneratorExtensionsTest.cs | 245 +++++++++++++++ .../PageLinkGeneratorExtensionsTest.cs | 245 +++++++++++++++ 7 files changed, 1148 insertions(+), 69 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs new file mode 100644 index 0000000000..01481bf488 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs @@ -0,0 +1,281 @@ +// 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.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Routing; +using System; + +namespace Microsoft.AspNetCore.Routing +{ + /// + /// Extension methods for using to generate links to MVC controllers. + /// + public static class ControllerLinkGeneratorExtensions + { + private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() + { + UseAmbientValues = true, + }; + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The associated with the current request. + /// + /// The action name. Used to resolve endpoints. Optional. If null is provided, the current action route value + /// will be used. + /// + /// + /// The controller name. Used to resolve endpoints. Optional. If null is provided, the current controller route value + /// will be used. + /// + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null if a URI cannot be created. + public static string GetPathByAction( + this LinkGenerator generator, + HttpContext httpContext, + string action = default, + string controller = default, + object values = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + var address = CreateAddress(httpContext, action, controller, values); + return generator.GetPathByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + pathBase, + fragment, + options); + } + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The action name. Used to resolve endpoints. + /// The controller name. Used to resolve endpoints. + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null if a URI cannot be created. + public static string GetPathByAction( + this LinkGenerator generator, + string action, + string controller, + object values = default, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + var address = CreateAddress(httpContext: null, action, controller, values); + return generator.GetPathByAddress(address, address.ExplicitValues, pathBase, fragment, options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The associated with the current request. + /// + /// The action name. Used to resolve endpoints. Optional. If null is provided, the current action route value + /// will be used. + /// + /// + /// The controller name. Used to resolve endpoints. Optional. If null is provided, the current controller route value + /// will be used. + /// + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A absolute URI, or null if a URI cannot be created. + public static string GetUriByAction( + this LinkGenerator generator, + HttpContext httpContext, + string action = default, + string controller = default, + object values = default, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + var address = CreateAddress(httpContext, action, controller, values); + return generator.GetUriByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + scheme, + host, + pathBase, + fragment, + options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The action name. Used to resolve endpoints. + /// The controller name. Used to resolve endpoints. + /// The route values. May be null. Used to resolve endpoints and expand parameters in the route template. + /// The URI scheme, applied to the resulting URI. + /// The URI host/authority, applied to the resulting URI. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A absolute URI, or null if a URI cannot be created. + public static string GetUriByAction( + this LinkGenerator generator, + string action, + string controller, + object values, + string scheme, + HostString host, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + var address = CreateAddress(httpContext: null, action, controller, values); + return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); + } + + /// + /// Gets a based on the provided , , and . + /// + /// The . + /// The action name. Used to resolve endpoints. + /// The controller name. Used to resolve endpoints. + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// A if one or more endpoints matching the address can be found, otherwise null. + /// + public static LinkGenerationTemplate GetTemplateByAction( + this LinkGenerator generator, + string action, + string controller, + object values = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + if (controller == null) + { + throw new ArgumentNullException(nameof(controller)); + } + + var address = CreateAddress(httpContext: null, action, controller, values); + return generator.GetTemplateByAddress(address, _templateOptions); + } + + private static RouteValuesAddress CreateAddress(HttpContext httpContext, string action, string controller, object values) + { + var explicitValues = new RouteValueDictionary(values); + var ambientValues = GetAmbientValues(httpContext); + + UrlHelperBase.NormalizeRouteValuesForAction(action, controller, explicitValues, ambientValues); + + return new RouteValuesAddress() + { + AmbientValues = ambientValues, + ExplicitValues = explicitValues + }; + } + + private static RouteValueDictionary GetAmbientValues(HttpContext httpContext) + { + return httpContext?.Features.Get()?.RouteValues; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs new file mode 100644 index 0000000000..ec508a9645 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs @@ -0,0 +1,268 @@ +// 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.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Mvc.Routing; +using System; + +namespace Microsoft.AspNetCore.Routing +{ + /// + /// Extension methods for using to generate links to Razor Pages. + /// + public static class PageLinkGeneratorExtensions + { + private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() + { + UseAmbientValues = true, + }; + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// The associated with the current request. + /// + /// The page name. Used to resolve endpoints. Optional. If null is provided, the current page route value + /// will be used. + /// + /// + /// The page handler name. Used to resolve endpoints. Optional. + /// + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null if a URI cannot be created. + public static string GetPathByPage( + this LinkGenerator generator, + HttpContext httpContext, + string page = default, + string handler = default, + object values = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + var address = CreateAddress(httpContext, page, handler, values); + return generator.GetPathByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + pathBase, + fragment, + options); + } + + /// + /// Generates a URI with an absolute path based on the provided values. + /// + /// The . + /// + /// The page name. Used to resolve endpoints. + /// + /// + /// The page handler name. Used to resolve endpoints. Optional. + /// + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A URI with an absolute path, or null if a URI cannot be created. + public static string GetPathByPage( + this LinkGenerator generator, + string page, + string handler = default, + object values = default, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + var address = CreateAddress(httpContext: null, page, handler, values); + return generator.GetPathByAddress(address, address.ExplicitValues, pathBase, fragment, options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The associated with the current request. + /// + /// The page name. Used to resolve endpoints. Optional. If null is provided, the current page route value + /// will be used. + /// + /// + /// The page handler name. Used to resolve endpoints. Optional. + /// + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// The URI scheme, applied to the resulting URI. Optional. If not provided, the value of will be used. + /// + /// + /// The URI host/authority, applied to the resulting URI. Optional. If not provided, the value will be used. + /// + /// + /// An optional URI path base. Prepended to the path in the resulting URI. If not provided, the value of will be used. + /// + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A absolute URI, or null if a URI cannot be created. + public static string GetUriByPage( + this LinkGenerator generator, + HttpContext httpContext, + string page = default, + string handler = default, + object values = default, + string scheme = default, + HostString? host = default, + PathString? pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + + var address = CreateAddress(httpContext, page, handler, values); + return generator.GetUriByAddress( + httpContext, + address, + address.ExplicitValues, + address.AmbientValues, + scheme, + host, + pathBase, + fragment, + options); + } + + /// + /// Generates an absolute URI based on the provided values. + /// + /// The . + /// The page name. Used to resolve endpoints. + /// The page handler name. May be null. + /// The route values. May be null. Used to resolve endpoints and expand parameters in the route template. + /// The URI scheme, applied to the resulting URI. + /// The URI host/authority, applied to the resulting URI. + /// An optional URI path base. Prepended to the path in the resulting URI. + /// A URI fragment. Optional. Appended to the resulting URI. + /// + /// An optional . Settings on provided object override the settings with matching + /// names from RouteOptions. + /// + /// A absolute URI, or null if a URI cannot be created. + public static string GetUriByPage( + this LinkGenerator generator, + string page, + string handler, + object values, + string scheme, + HostString host, + PathString pathBase = default, + FragmentString fragment = default, + LinkOptions options = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + var address = CreateAddress(httpContext: null, page, handler, values); + return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); + } + + /// + /// Gets a based on the provided , , and . + /// + /// The . + /// The page name. Used to resolve endpoints. + /// The page handler name. Optional. + /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. + /// + /// A if one or more endpoints matching the address can be found, otherwise null. + /// + public static LinkGenerationTemplate GetTemplateByPage( + this LinkGenerator generator, + string page, + string handler = default, + object values = default) + { + if (generator == null) + { + throw new ArgumentNullException(nameof(generator)); + } + + if (page == null) + { + throw new ArgumentNullException(nameof(page)); + } + + var address = CreateAddress(httpContext: null, page, handler, values); + return generator.GetTemplateByAddress(address, _templateOptions); + } + + private static RouteValuesAddress CreateAddress(HttpContext httpContext, string page, string handler, object values) + { + var explicitValues = new RouteValueDictionary(values); + var ambientValues = GetAmbientValues(httpContext); + + UrlHelperBase.NormalizeRouteValuesForPage(context: null, page, handler, explicitValues, ambientValues); + + return new RouteValuesAddress() + { + AmbientValues = ambientValues, + ExplicitValues = explicitValues + }; + } + + private static RouteValueDictionary GetAmbientValues(HttpContext httpContext) + { + return httpContext?.Features.Get()?.RouteValues; + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs index 8bab2581d4..33f7d36f3e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs @@ -44,31 +44,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var valuesDictionary = GetValuesDictionary(actionContext.Values); - if (actionContext.Action == null) - { - if (!valuesDictionary.ContainsKey("action") && - AmbientValues.TryGetValue("action", out var action)) - { - valuesDictionary["action"] = action; - } - } - else - { - valuesDictionary["action"] = actionContext.Action; - } - - if (actionContext.Controller == null) - { - if (!valuesDictionary.ContainsKey("controller") && - AmbientValues.TryGetValue("controller", out var controller)) - { - valuesDictionary["controller"] = controller; - } - } - else - { - valuesDictionary["controller"] = actionContext.Controller; - } + NormalizeRouteValuesForAction(actionContext.Action, actionContext.Controller, valuesDictionary, AmbientValues); var virtualPathData = GetVirtualPathData(routeName: null, values: valuesDictionary); return GenerateUrl(actionContext.Protocol, actionContext.Host, virtualPathData, actionContext.Fragment); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs index a68c9c988b..5c48596532 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs @@ -3,8 +3,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Text; using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Core; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; namespace Microsoft.AspNetCore.Mvc.Routing @@ -263,6 +267,109 @@ namespace Microsoft.AspNetCore.Mvc.Routing } } + internal static void NormalizeRouteValuesForAction( + string action, + string controller, + RouteValueDictionary values, + RouteValueDictionary ambientValues) + { + object obj = null; + if (action == null) + { + if (!values.ContainsKey("action") && + (ambientValues?.TryGetValue("action", out obj) ?? false)) + { + values["action"] = obj; + } + } + else + { + values["action"] = action; + } + + if (controller == null) + { + if (!values.ContainsKey("controller") && + (ambientValues?.TryGetValue("controller", out obj) ?? false)) + { + values["controller"] = obj; + } + } + else + { + values["controller"] = controller; + } + } + + internal static void NormalizeRouteValuesForPage( + ActionContext context, + string page, + string handler, + RouteValueDictionary values, + RouteValueDictionary ambientValues) + { + object value = null; + if (string.IsNullOrEmpty(page)) + { + if (!values.ContainsKey("page") && + (ambientValues?.TryGetValue("page", out value) ?? false)) + { + values["page"] = value; + } + } + else + { + values["page"] = CalculatePageName(context, ambientValues, page); + } + + if (string.IsNullOrEmpty(handler)) + { + if (!values.ContainsKey("handler") && + (ambientValues?.ContainsKey("handler") ?? false)) + { + // Clear out form action unless it's explicitly specified in the routeValues. + values["handler"] = null; + } + } + else + { + values["handler"] = handler; + } + } + + private static object CalculatePageName(ActionContext context, RouteValueDictionary ambientValues, string pageName) + { + Debug.Assert(pageName.Length > 0); + // Paths not qualified with a leading slash are treated as relative to the current page. + if (pageName[0] != '/') + { + // OK now we should get the best 'normalized' version of the page route value that we can. + string currentPagePath; + if (context != null) + { + currentPagePath = NormalizedRouteValue.GetNormalizedRouteValue(context, "page"); + } + else if (ambientValues != null) + { + currentPagePath = ambientValues["page"]?.ToString(); + } + else + { + currentPagePath = null; + } + + if (string.IsNullOrEmpty(currentPagePath)) + { + // Disallow the use sibling page routing, a Razor page specific feature, from a non-page action. + throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName)); + } + + return ViewEnginePath.CombinePath(currentPagePath, pageName); + } + + return pageName; + } + // for unit testing internal static void AppendPathAndFragment(StringBuilder builder, PathString pathBase, string virtualPath, string fragment) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs index 42da1bc1ac..e790b879ef 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -444,32 +444,8 @@ namespace Microsoft.AspNetCore.Mvc var routeValues = new RouteValueDictionary(values); var ambientValues = urlHelper.ActionContext.RouteData.Values; - if (string.IsNullOrEmpty(pageName)) - { - if (!routeValues.ContainsKey("page") && - ambientValues.TryGetValue("page", out var value)) - { - routeValues["page"] = value; - } - } - else - { - routeValues["page"] = CalculatePageName(urlHelper.ActionContext, pageName); - } - if (string.IsNullOrEmpty(pageHandler)) - { - if (!routeValues.ContainsKey("handler") && - ambientValues.TryGetValue("handler", out var handler)) - { - // Clear out form action unless it's explicitly specified in the routeValues. - routeValues["handler"] = null; - } - } - else - { - routeValues["handler"] = pageHandler; - } + UrlHelperBase.NormalizeRouteValuesForPage(urlHelper.ActionContext, pageName, pageHandler, routeValues, ambientValues); return urlHelper.RouteUrl( routeName: null, @@ -478,24 +454,5 @@ namespace Microsoft.AspNetCore.Mvc host: host, fragment: fragment); } - - private static object CalculatePageName(ActionContext actionContext, string pageName) - { - Debug.Assert(pageName.Length > 0); - // Paths not qualified with a leading slash are treated as relative to the current page. - if (pageName[0] != '/') - { - var currentPagePath = NormalizedRouteValue.GetNormalizedRouteValue(actionContext, "page"); - if (string.IsNullOrEmpty(currentPagePath)) - { - // Disallow the use sibling page routing, a Razor page specific feature, from a non-page action. - throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName)); - } - - return ViewEnginePath.CombinePath(currentPagePath, pageName); - } - - return pageName; - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs new file mode 100644 index 0000000000..5d0f01fafb --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs @@ -0,0 +1,245 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.ObjectPool; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class ControllerLinkGeneratorExtensionsTest + { + [Fact] + public void GetPathByAction_WithHttpContext_PromotesAmbientValues() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(new { controller = "Home", }); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var path = linkGenerator.GetPathByAction( + httpContext, + action: "Index", + values: new RouteValueDictionary(new { query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetPathByAction_WithoutHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var path = linkGenerator.GetPathByAction( + action: "Index", + controller: "Home", + values: new RouteValueDictionary(new { query = "some?query" }), + new PathString("/Foo/Bar?encodeme?"), + new FragmentString("#Fragment?"), + new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetPathByAction_WithHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var path = linkGenerator.GetPathByAction( + httpContext, + action: "Index", + controller: "Home", + values: new RouteValueDictionary(new { query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetUriByAction_WithoutHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var path = linkGenerator.GetUriByAction( + action: "Index", + controller: "Home", + values: new RouteValueDictionary(new { query = "some?query" }), + "http", + new HostString("example.com"), + new PathString("/Foo/Bar?encodeme?"), + new FragmentString("#Fragment?"), + new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetUriByAction_WithHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + defaults: new { controller = "Home", action = "Index", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(new { controller = "Home", action = "Index", }); + httpContext.Request.Scheme = "http"; + httpContext.Request.Host = new HostString("example.com"); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var uri = linkGenerator.GetUriByAction( + httpContext, + values: new RouteValueDictionary(new { query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri); + } + + [Fact] + public void GetTemplateByAction_CreatesTemplate() + { + // Arrange + var endpoint1 = CreateEndpoint( + "Home/Index/{id}", + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + var endpoint2 = CreateEndpoint( + "Home/Index/{id?}", + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var template = linkGenerator.GetTemplateByAction(action: "Index", controller: "Home"); + + // Assert + Assert.NotNull(template); + Assert.Equal("/Home/Index/17", template.GetPath(new { id = 17 })); + } + + private RouteEndpoint CreateEndpoint( + string template, + object defaults = null, + object requiredValues = null, + int order = 0, + object[] metadata = null) + { + return new RouteEndpoint( + (httpContext) => Task.CompletedTask, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), + order, + new EndpointMetadataCollection(metadata ?? Array.Empty()), + null); + } + + private IServiceProvider CreateServices(IEnumerable endpoints) + { + if (endpoints == null) + { + endpoints = Enumerable.Empty(); + } + + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(); + services.AddRouting(); + services + .AddSingleton() + .AddSingleton(UrlEncoder.Default); + services.TryAddEnumerable(ServiceDescriptor.Singleton(new DefaultEndpointDataSource(endpoints))); + return services.BuildServiceProvider(); + } + + private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) + { + var services = CreateServices(endpoints); + return services.GetRequiredService(); + } + + private HttpContext CreateHttpContext(object ambientValues = null) + { + var httpContext = new DefaultHttpContext(); + + var feature = new EndpointFeature + { + RouteValues = new RouteValueDictionary(ambientValues) + }; + + httpContext.Features.Set(feature); + httpContext.Features.Set(feature); + return httpContext; + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs new file mode 100644 index 0000000000..f07f150472 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs @@ -0,0 +1,245 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text.Encodings.Web; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Features; +using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.ObjectPool; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class PageLinkGeneratorExtensionsTest + { + [Fact] + public void GetPathByPage_WithHttpContext_PromotesAmbientValues() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers/{handler?}", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(new { page = "/About", id = 17, }); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var path = linkGenerator.GetPathByPage( + httpContext, + values: new RouteValueDictionary(new { id = 18, query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/About/18/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetPathByPage_WithoutHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers/{handler?}", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var path = linkGenerator.GetPathByPage( + page: "/Admin/ManageUsers", + handler: "Delete", + values: new RouteValueDictionary(new { user = "jamesnk", query = "some?query" }), + new PathString("/Foo/Bar?encodeme?"), + new FragmentString("#Fragment?"), + new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/Admin/ManageUsers/Delete/?user=jamesnk&query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetPathByPage_WithHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(new { page = "/Admin/ManageUsers", handler = "DeleteUser", }); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var path = linkGenerator.GetPathByPage( + httpContext, + page: "/About", + values: new RouteValueDictionary(new { id = 19, query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("/Foo/Bar%3Fencodeme%3F/About/19/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetUriByPage_WithoutHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var path = linkGenerator.GetUriByPage( + page: "/About", + handler: null, + values: new RouteValueDictionary(new { id = 19, query = "some?query" }), + "http", + new HostString("example.com"), + new PathString("/Foo/Bar?encodeme?"), + new FragmentString("#Fragment?"), + new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/About/19/?query=some%3Fquery#Fragment?", path); + } + + [Fact] + public void GetUriByPage_WithHttpContext_WithPathBaseAndFragment() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + var httpContext = CreateHttpContext(new { page = "/Admin/ManageUsers", }); + httpContext.Request.Scheme = "http"; + httpContext.Request.Host = new HostString("example.com"); + httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); + + // Act + var uri = linkGenerator.GetUriByPage( + httpContext, + values: new RouteValueDictionary(new { query = "some?query" }), + fragment: new FragmentString("#Fragment?"), + options: new LinkOptions() { AppendTrailingSlash = true, }); + + // Assert + Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Admin/ManageUsers/?query=some%3Fquery#Fragment?", uri); + } + + [Fact] + public void GetTemplateByAction_CreatesTemplate() + { + // Arrange + var endpoint1 = CreateEndpoint( + "About/{id}", + defaults: new { page = "/About", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); + var endpoint2 = CreateEndpoint( + "Admin/ManageUsers", + defaults: new { page = "/Admin/ManageUsers", }, + metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); + + var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); + + // Act + var template = linkGenerator.GetTemplateByPage(page: "/About"); + + // Assert + Assert.NotNull(template); + Assert.Equal("/About/17", template.GetPath(new { id = 17 })); + } + + private RouteEndpoint CreateEndpoint( + string template, + object defaults = null, + object requiredValues = null, + int order = 0, + object[] metadata = null) + { + return new RouteEndpoint( + (httpContext) => Task.CompletedTask, + RoutePatternFactory.Parse(template, defaults, parameterPolicies: null), + order, + new EndpointMetadataCollection(metadata ?? Array.Empty()), + null); + } + + private IServiceProvider CreateServices(IEnumerable endpoints) + { + if (endpoints == null) + { + endpoints = Enumerable.Empty(); + } + + var services = new ServiceCollection(); + services.AddOptions(); + services.AddLogging(); + services.AddRouting(); + services + .AddSingleton() + .AddSingleton(UrlEncoder.Default); + services.TryAddEnumerable(ServiceDescriptor.Singleton(new DefaultEndpointDataSource(endpoints))); + return services.BuildServiceProvider(); + } + + private LinkGenerator CreateLinkGenerator(params Endpoint[] endpoints) + { + var services = CreateServices(endpoints); + return services.GetRequiredService(); + } + + private HttpContext CreateHttpContext(object ambientValues = null) + { + var httpContext = new DefaultHttpContext(); + + var feature = new EndpointFeature + { + RouteValues = new RouteValueDictionary(ambientValues) + }; + + httpContext.Features.Set(feature); + httpContext.Features.Set(feature); + return httpContext; + } + } +} \ No newline at end of file From 5b8b3a00674871dfe081e77696aa22e85c911f67 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 26 Sep 2018 09:38:23 -0700 Subject: [PATCH 257/316] Reference Microsoft.NET.Sdk.Razor in projects with Razor files (during benchmark builds) --- benchmarkapps/BasicViews/BasicViews.csproj | 1 + benchmarkapps/RazorRendering/RazorRendering.csproj | 1 + 2 files changed, 2 insertions(+) diff --git a/benchmarkapps/BasicViews/BasicViews.csproj b/benchmarkapps/BasicViews/BasicViews.csproj index 9db233657d..6e62f39598 100644 --- a/benchmarkapps/BasicViews/BasicViews.csproj +++ b/benchmarkapps/BasicViews/BasicViews.csproj @@ -39,5 +39,6 @@ --> + diff --git a/benchmarkapps/RazorRendering/RazorRendering.csproj b/benchmarkapps/RazorRendering/RazorRendering.csproj index fee2d62006..dc531b3fdf 100644 --- a/benchmarkapps/RazorRendering/RazorRendering.csproj +++ b/benchmarkapps/RazorRendering/RazorRendering.csproj @@ -17,6 +17,7 @@ --> + From 8311fd870b5594ab547a8cc6ca3b7b87fda9effd Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 26 Sep 2018 15:51:49 -0700 Subject: [PATCH 258/316] Include the response type in ProducesResponseType for client errors (#8490) * Include the response type in ProducesResponseType for client errors * Refactor ActualApiResponseMetadata discovery in to a separate more manageable type * Annotate action result ctors and helper methods that specify the "object" value with attribute * Modify the discovery of parameters to match ActionResultObjectValueAttribute and ActionResultStatusCodeAttribute by name to allow users to write and annotate custom helper methods and action results, a la NotNullAttribute. Fixes #8345 --- .../ActualApiResponseMetadata.cs | 9 +- .../ActualApiResponseMetadataFactory.cs | 300 ++++++++++++++ .../AddResponseTypeAttributeCodeFixAction.cs | 68 +++- ...ireExplicitModelValidationCheckAnalyzer.cs | 2 +- .../ApiControllerSymbolCache.cs | 9 +- .../ApiConventionAnalyzer.cs | 2 +- .../ApiSymbolNames.cs | 10 +- .../SymbolApiResponseMetadataProvider.cs | 289 ++------------ .../AcceptedAtActionResult.cs | 2 +- .../AcceptedAtRouteResult.cs | 4 +- .../AcceptedResult.cs | 4 +- .../BadRequestObjectResult.cs | 4 +- .../ConflictObjectResult.cs | 4 +- .../ControllerBase.cs | 66 ++-- .../CreatedAtActionResult.cs | 2 +- .../CreatedAtRouteResult.cs | 4 +- .../ActionResultObjectValueAttribute.cs | 27 ++ .../ActionResultStatusCodeAttribute.cs | 26 ++ .../StatusCodeValueAttribute.cs | 12 - .../NotFoundObjectResult.cs | 2 +- .../ObjectResult.cs | 1 + .../StatusCodeResult.cs | 2 +- .../UnauthorizedObjectResult.cs | 2 +- .../UnprocessableEntityObjectResult.cs | 4 +- .../ActualApiResponseMetadataFactoryTest.cs | 369 ++++++++++++++++++ ...AttributeCodeFixProviderIntegrationTest.cs | 6 + .../ApiConventionAnalyzerIntegrationTest.cs | 65 +++ .../DeclaredApiResponseMetadataTest.cs | 18 +- .../SymbolApiResponseMetadataProviderTest.cs | 234 +++-------- .../GetDefaultStatusCodeTest.cs | 0 .../InspectReturnExpressionTests.cs | 81 ++++ .../TryGetActualResponseMetadataTests.cs | 2 +- ...FixAddsMissingStatusCodesAndTypes.Input.cs | 25 ++ ...ixAddsMissingStatusCodesAndTypes.Output.cs | 28 ++ ...nseTypeWhenDifferentFromErrorType.Input.cs | 26 ++ ...seTypeWhenDifferentFromErrorType.Output.cs | 32 ++ ...roblemDetails_IfNoAttributeIsDiscovered.cs | 7 + ...ResponseType_ReturnsTypeDefinedAtAction.cs | 13 + ...sponseType_ReturnsTypeDefinedAtAssembly.cs | 13 + ...onseType_ReturnsTypeDefinedAtController.cs | 10 + .../GetResponseMetadataTests.cs | 2 +- ...etadata_IfReturnedTypeIsNotActionResult.cs | 12 - ...efaultStatusCodeAttributeOnActionResult.cs | 10 - 43 files changed, 1254 insertions(+), 554 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs create mode 100644 test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs rename test/Mvc.Api.Analyzers.Test/TestFiles/{SymbolApiResponseMetadataProviderTest => ActualApiResponseMetadataFactoryTest}/GetDefaultStatusCodeTest.cs (100%) create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs rename test/Mvc.Api.Analyzers.Test/TestFiles/{SymbolApiResponseMetadataProviderTest => ActualApiResponseMetadataFactoryTest}/TryGetActualResponseMetadataTests.cs (92%) create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs create mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs delete mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs delete mode 100644 test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs index 15f790450e..018967cb10 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs @@ -1,6 +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.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers @@ -9,16 +10,18 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { private readonly int? _statusCode; - public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement) + public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement, ITypeSymbol returnType) { ReturnStatement = returnStatement; + ReturnType = returnType; _statusCode = null; } - public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement, int statusCode) + public ActualApiResponseMetadata(ReturnStatementSyntax returnStatement, int statusCode, ITypeSymbol returnType) { ReturnStatement = returnStatement; _statusCode = statusCode; + ReturnType = returnType; } public ReturnStatementSyntax ReturnStatement { get; } @@ -26,5 +29,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public int StatusCode => _statusCode.Value; public bool IsDefaultResponse => _statusCode == null; + + public ITypeSymbol ReturnType { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs new file mode 100644 index 0000000000..83e1c78546 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs @@ -0,0 +1,300 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + public static class ActualApiResponseMetadataFactory + { + private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; + + /// + /// This method looks at individual return statments and attempts to parse the status code and the return type. + /// Given a for an action, this method inspects return statements in the body. + /// If the returned type is not assignable from IActionResult, it assumes that an "object" value is being returned. e.g. return new Person(); + /// For return statements returning an action result, it attempts to infer the status code and return type. Helper methods in controller, + /// values set in initializer and new-ing up an IActionResult instance are supported. + /// + internal static bool TryGetActualResponseMetadata( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + MethodDeclarationSyntax methodSyntax, + CancellationToken cancellationToken, + out IList actualResponseMetadata) + { + actualResponseMetadata = new List(); + + var allReturnStatementsReadable = true; + + foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) + { + if (returnStatementSyntax.IsMissing || returnStatementSyntax.Expression == null || returnStatementSyntax.Expression.IsMissing) + { + // Ignore malformed return statements. + allReturnStatementsReadable = false; + continue; + } + + var responseMetadata = InspectReturnStatementSyntax( + symbolCache, + semanticModel, + returnStatementSyntax, + cancellationToken); + + if (responseMetadata != null) + { + actualResponseMetadata.Add(responseMetadata.Value); + } + else + { + allReturnStatementsReadable = false; + } + } + + return allReturnStatementsReadable; + } + + internal static ActualApiResponseMetadata? InspectReturnStatementSyntax( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + ReturnStatementSyntax returnStatementSyntax, + CancellationToken cancellationToken) + { + var returnExpression = returnStatementSyntax.Expression; + var typeInfo = semanticModel.GetTypeInfo(returnExpression, cancellationToken); + if (typeInfo.Type == null || typeInfo.Type.TypeKind == TypeKind.Error) + { + return null; + } + + var statementReturnType = typeInfo.Type; + + if (!symbolCache.IActionResult.IsAssignableFrom(statementReturnType)) + { + // Return expression is not an instance of IActionResult. Must be returning the "model". + return new ActualApiResponseMetadata(returnStatementSyntax, statementReturnType); + } + + var defaultStatusCodeAttribute = statementReturnType + .GetAttributes(symbolCache.DefaultStatusCodeAttribute, inherit: true) + .FirstOrDefault(); + + var statusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); + ITypeSymbol returnType = null; + switch (returnExpression) + { + case InvocationExpressionSyntax invocation: + { + // Covers the 'return StatusCode(200)' case. + var result = InspectMethodArguments(symbolCache, semanticModel, invocation.Expression, invocation.ArgumentList, cancellationToken); + statusCode = result.statusCode ?? statusCode; + returnType = result.returnType; + break; + } + + case ObjectCreationExpressionSyntax creation: + { + // Read values from 'return new StatusCodeResult(200) case. + var result = InspectMethodArguments(symbolCache, semanticModel, creation, creation.ArgumentList, cancellationToken); + statusCode = result.statusCode ?? statusCode; + returnType = result.returnType; + + // Read values from property assignments e.g. 'return new ObjectResult(...) { StatusCode = 200 }'. + // Property assignments override constructor assigned values and defaults. + result = InspectInitializers(symbolCache, semanticModel, creation.Initializer, cancellationToken); + statusCode = result.statusCode ?? statusCode; + returnType = result.returnType ?? returnType; + break; + } + } + + if (statusCode == null) + { + return null; + } + + return new ActualApiResponseMetadata(returnStatementSyntax, statusCode.Value, returnType); + } + + private static (int? statusCode, ITypeSymbol returnType) InspectInitializers( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + InitializerExpressionSyntax initializer, + CancellationToken cancellationToken) + { + int? statusCode = null; + ITypeSymbol typeSymbol = null; + + for (var i = 0; initializer != null && i < initializer.Expressions.Count; i++) + { + var expression = initializer.Expressions[i]; + + if (!(expression is AssignmentExpressionSyntax assignment) || + !(assignment.Left is IdentifierNameSyntax identifier)) + { + continue; + } + + var symbolInfo = semanticModel.GetSymbolInfo(identifier, cancellationToken); + if (symbolInfo.Symbol is IPropertySymbol property) + { + if (IsInterfaceImplementation(property, symbolCache.StatusCodeActionResultStatusProperty) && + TryGetExpressionStatusCode(semanticModel, assignment.Right, cancellationToken, out var statusCodeValue)) + { + // Look for assignments to IStatusCodeActionResult.StatusCode + statusCode = statusCodeValue; + } + else if (HasAttributeNamed(property, ApiSymbolNames.ActionResultObjectValueAttribute)) + { + // Look for assignment to a property annotated with [ActionResultObjectValue] + typeSymbol = GetExpressionObjectType(semanticModel, assignment.Right, cancellationToken); + } + } + } + + return (statusCode, typeSymbol); + } + + private static (int? statusCode, ITypeSymbol returnType) InspectMethodArguments( + in ApiControllerSymbolCache symbolCache, + SemanticModel semanticModel, + ExpressionSyntax expression, + BaseArgumentListSyntax argumentList, + CancellationToken cancellationToken) + { + int? statusCode = null; + ITypeSymbol typeSymbol = null; + + var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); + + if (symbolInfo.Symbol is IMethodSymbol method) + { + for (var i = 0; i < method.Parameters.Length; i++) + { + var parameter = method.Parameters[i]; + if (HasAttributeNamed(parameter, ApiSymbolNames.ActionResultStatusCodeAttribute)) + { + var argument = argumentList.Arguments[parameter.Ordinal]; + if (TryGetExpressionStatusCode(semanticModel, argument.Expression, cancellationToken, out var statusCodeValue)) + { + statusCode = statusCodeValue; + } + } + + if (HasAttributeNamed(parameter, ApiSymbolNames.ActionResultObjectValueAttribute)) + { + var argument = argumentList.Arguments[parameter.Ordinal]; + typeSymbol = GetExpressionObjectType(semanticModel, argument.Expression, cancellationToken); + } + } + } + + return (statusCode, typeSymbol); + } + + private static ITypeSymbol GetExpressionObjectType(SemanticModel semanticModel, ExpressionSyntax expression, CancellationToken cancellationToken) + { + var typeInfo = semanticModel.GetTypeInfo(expression, cancellationToken); + return typeInfo.Type; + } + + private static bool TryGetExpressionStatusCode( + SemanticModel semanticModel, + ExpressionSyntax expression, + CancellationToken cancellationToken, + out int statusCode) + { + if (expression is LiteralExpressionSyntax literal && literal.Token.Value is int literalStatusCode) + { + // Covers the 'return StatusCode(200)' case. + statusCode = literalStatusCode; + return true; + } + + if (expression is IdentifierNameSyntax || expression is MemberAccessExpressionSyntax) + { + var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); + + if (symbolInfo.Symbol is IFieldSymbol field && field.HasConstantValue && field.ConstantValue is int constantStatusCode) + { + // Covers the 'return StatusCode(StatusCodes.Status200OK)' case. + // It also covers the 'return StatusCode(StatusCode)' case, where 'StatusCode' is a constant field. + statusCode = constantStatusCode; + return true; + } + + if (symbolInfo.Symbol is ILocalSymbol local && local.HasConstantValue && local.ConstantValue is int localStatusCode) + { + // Covers the 'return StatusCode(statusCode)' case, where 'statusCode' is a local constant. + statusCode = localStatusCode; + return true; + } + } + + statusCode = default; + return false; + } + + private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) + { + return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && + !syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) && + !syntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression); + } + + internal static int? GetDefaultStatusCode(AttributeData attribute) + { + if (attribute != null && + attribute.ConstructorArguments.Length == 1 && + attribute.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && + attribute.ConstructorArguments[0].Value is int statusCode) + { + return statusCode; + } + + return null; + } + + private static bool IsInterfaceImplementation(IPropertySymbol property, IPropertySymbol statusCodeActionResultStatusProperty) + { + if (property.Name != statusCodeActionResultStatusProperty.Name) + { + return false; + } + + for (var i = 0; i < property.ExplicitInterfaceImplementations.Length; i++) + { + if (property.ExplicitInterfaceImplementations[i] == statusCodeActionResultStatusProperty) + { + return true; + } + } + + var implementedProperty = property.ContainingType.FindImplementationForInterfaceMember(statusCodeActionResultStatusProperty); + return implementedProperty == property; + } + + private static bool HasAttributeNamed(ISymbol symbol, string attributeName) + { + var attributes = symbol.GetAttributes(); + var length = attributes.Length; + for (var i = 0; i < length; i++) + { + if (attributes[i].AttributeClass.Name == attributeName) + { + return true; + } + } + + return false; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs index 9457429a12..e7601b6b7f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs @@ -15,6 +15,17 @@ using Microsoft.CodeAnalysis.Simplification; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { + /// + /// A that adds one or more ProducesResponseType attributes on the action. + /// 1) It get status codes from ProducesResponseType, ProducesDefaultResponseType, and conventions applied to the action to get the declared metadata. + /// 2) It inspects return statements to get actual metadata. + /// Diffing the two gets us a list of undocumented status codes. + /// We'll attempt to generate a [ProducesResponseType(typeof(SomeModel), 4xx)] if + /// a) the status code is 4xx or later. + /// b) the return statement included a return type. + /// c) the return type wasn't the error type (specified by ProducesErrorResponseType or implicit ProblemDetails) + /// In all other cases, we generate [ProducesResponseType(StatusCode)] + /// internal sealed class AddResponseTypeAttributeCodeFixAction : CodeAction { private readonly Document _document; @@ -32,9 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { var context = await CreateCodeActionContext(cancellationToken).ConfigureAwait(false); var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(context.SymbolCache, context.Method); + var errorResponseType = SymbolApiResponseMetadataProvider.GetErrorResponseType(context.SymbolCache, context.Method); - var statusCodes = CalculateStatusCodesToApply(context, declaredResponseMetadata); - if (statusCodes.Count == 0) + var results = CalculateStatusCodesToApply(context, declaredResponseMetadata); + if (results.Count == 0) { return _document; } @@ -42,9 +54,22 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers var documentEditor = await DocumentEditor.CreateAsync(_document, cancellationToken).ConfigureAwait(false); var addUsingDirective = false; - foreach (var statusCode in statusCodes.OrderBy(s => s)) + foreach (var (statusCode, returnType) in results.OrderBy(s => s.statusCode)) { - documentEditor.AddAttribute(context.MethodSyntax, CreateProducesResponseTypeAttribute(context, statusCode, out var addUsing)); + AttributeSyntax attributeSyntax; + bool addUsing; + + if (statusCode >= 400 && returnType != null && returnType != errorResponseType) + { + // If a returnType was discovered and is different from the errorResponseType, use it in the result. + attributeSyntax = CreateProducesResponseTypeAttribute(context, statusCode, returnType, out addUsing); + } + else + { + attributeSyntax = CreateProducesResponseTypeAttribute(context, statusCode, out addUsing); + } + + documentEditor.AddAttribute(context.MethodSyntax, attributeSyntax); addUsingDirective |= addUsing; } @@ -103,7 +128,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return codeActionContext; } - private static Dictionary GetStatusCodeConstants(INamespaceOrTypeSymbol statusCodesType) + private static Dictionary GetStatusCodeConstants(INamedTypeSymbol statusCodesType) { var statusCodeConstants = new Dictionary(); @@ -125,15 +150,15 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return statusCodeConstants; } - private ICollection CalculateStatusCodesToApply(CodeActionContext context, IList declaredResponseMetadata) + private ICollection<(int statusCode, ITypeSymbol typeSymbol)> CalculateStatusCodesToApply(in CodeActionContext context, IList declaredResponseMetadata) { - if (!SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata)) + if (!ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(context.SymbolCache, context.SemanticModel, context.MethodSyntax, context.CancellationToken, out var actualResponseMetadata)) { // If we cannot parse metadata correctly, don't offer fixes. - return Array.Empty(); + return Array.Empty<(int, ITypeSymbol)>(); } - var statusCodes = new HashSet(); + var statusCodes = new Dictionary(); foreach (var metadata in actualResponseMetadata) { if (DeclaredApiResponseMetadata.TryGetDeclaredMetadata(declaredResponseMetadata, metadata, result: out var declaredMetadata) && @@ -143,20 +168,39 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers continue; } - statusCodes.Add(metadata.IsDefaultResponse ? 200 : metadata.StatusCode); + var statusCode = metadata.IsDefaultResponse ? 200 : metadata.StatusCode; + statusCodes.Add(statusCode, (statusCode, metadata.ReturnType)); } - return statusCodes; + return statusCodes.Values; } - private static AttributeSyntax CreateProducesResponseTypeAttribute(CodeActionContext context, int statusCode, out bool addUsingDirective) + private static AttributeSyntax CreateProducesResponseTypeAttribute(in CodeActionContext context, int statusCode, out bool addUsingDirective) { + // [ProducesResponseType(StatusCodes.Status400NotFound)] var statusCodeSyntax = CreateStatusCodeSyntax(context, statusCode, out addUsingDirective); return SyntaxFactory.Attribute( SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute) .WithAdditionalAnnotations(Simplifier.Annotation), SyntaxFactory.AttributeArgumentList().AddArguments( + + SyntaxFactory.AttributeArgument(statusCodeSyntax))); + } + + private static AttributeSyntax CreateProducesResponseTypeAttribute(in CodeActionContext context, int statusCode, ITypeSymbol typeSymbol, out bool addUsingDirective) + { + // [ProducesResponseType(typeof(ReturnType), StatusCodes.Status400NotFound)] + var statusCodeSyntax = CreateStatusCodeSyntax(context, statusCode, out addUsingDirective); + var responseTypeAttribute = SyntaxFactory.TypeOfExpression( + SyntaxFactory.ParseTypeName(typeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)) + .WithAdditionalAnnotations(Simplifier.Annotation)); + + return SyntaxFactory.Attribute( + SyntaxFactory.ParseName(ApiSymbolNames.ProducesResponseTypeAttribute) + .WithAdditionalAnnotations(Simplifier.Annotation), + SyntaxFactory.AttributeArgumentList().AddArguments( + SyntaxFactory.AttributeArgument(responseTypeAttribute), SyntaxFactory.AttributeArgument(statusCodeSyntax))); } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs index 9928708f73..9a8dc1c714 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs @@ -103,7 +103,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } var returnStatementSyntax = (ReturnStatementSyntax)returnOperation.Syntax; - var actualMetadata = SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( + var actualMetadata = ActualApiResponseMetadataFactory.InspectReturnStatementSyntax( symbolCache, semanticModel, returnStatementSyntax, diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs index 7cbec7eb36..37f6b26291 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers @@ -22,11 +21,11 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers ModelStateDictionary = compilation.GetTypeByMetadataName(ApiSymbolNames.ModelStateDictionary); NonActionAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.NonActionAttribute); NonControllerAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.NonControllerAttribute); + ProblemDetails = compilation.GetTypeByMetadataName(ApiSymbolNames.ProblemDetails); ProducesDefaultResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesDefaultResponseTypeAttribute); + ProducesErrorResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesErrorResponseTypeAttribute); ProducesResponseTypeAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.ProducesResponseTypeAttribute); - StatusCodeValueAttribute = compilation.GetTypeByMetadataName(ApiSymbolNames.StatusCodeValueAttribute); - var statusCodeActionResult = compilation.GetTypeByMetadataName(ApiSymbolNames.IStatusCodeActionResult); StatusCodeActionResultStatusProperty = (IPropertySymbol)statusCodeActionResult?.GetMembers("StatusCode")[0]; @@ -61,10 +60,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public INamedTypeSymbol NonControllerAttribute { get; } + public INamedTypeSymbol ProblemDetails { get; } + public INamedTypeSymbol ProducesDefaultResponseTypeAttribute { get; } public INamedTypeSymbol ProducesResponseTypeAttribute { get; } - public INamedTypeSymbol StatusCodeValueAttribute { get; } + public INamedTypeSymbol ProducesErrorResponseTypeAttribute { get; } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs index 27e5439330..6b92ee4c8f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs @@ -51,7 +51,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } var declaredResponseMetadata = SymbolApiResponseMetadataProvider.GetDeclaredResponseMetadata(symbolCache, method); - var hasUnreadableStatusCodes = !SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata); + var hasUnreadableStatusCodes = !ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, cancellationToken, out var actualResponseMetadata); var hasUndocumentedStatusCodes = false; foreach (var actualMetadata in actualResponseMetadata) diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs index 135d742feb..849d07fa0b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs @@ -5,6 +5,10 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { internal static class ApiSymbolNames { + public const string ActionResultStatusCodeAttribute = "ActionResultStatusCodeAttribute"; + + public const string ActionResultObjectValueAttribute = "ActionResultObjectValueAttribute"; + public const string AllowAnonymousAttribute = "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute"; public const string ApiConventionMethodAttribute = "Microsoft.AspNetCore.Mvc.ApiConventionMethodAttribute"; @@ -31,12 +35,14 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public const string NonControllerAttribute = "Microsoft.AspNetCore.Mvc.NonControllerAttribute"; + public const string ProblemDetails = "Microsoft.AspNetCore.Mvc.ProblemDetails"; + public const string ProducesDefaultResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute"; + public const string ProducesErrorResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesErrorResponseTypeAttribute"; + public const string ProducesResponseTypeAttribute = "Microsoft.AspNetCore.Mvc.ProducesResponseTypeAttribute"; public const string HttpStatusCodes = "Microsoft.AspNetCore.Http.StatusCodes"; - - public const string StatusCodeValueAttribute = "Microsoft.AspNetCore.Mvc.Infrastructure.StatusCodeValueAttribute"; } } diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs index 7e3744757e..2be586958d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs @@ -4,10 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { @@ -15,14 +12,13 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { private const string StatusCodeProperty = "StatusCode"; private const string StatusCodeConstructorParameter = "statusCode"; - private static readonly Func _shouldDescendIntoChildren = ShouldDescendIntoChildren; private static readonly IList DefaultResponseMetadatas = new[] { DeclaredApiResponseMetadata.ImplicitResponse, }; - internal static IList GetDeclaredResponseMetadata( - ApiControllerSymbolCache symbolCache, + public static IList GetDeclaredResponseMetadata( + in ApiControllerSymbolCache symbolCache, IMethodSymbol method) { var metadataItems = GetResponseMetadataFromMethodAttributes(symbolCache, method); @@ -44,8 +40,29 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return metadataItems; } + public static ITypeSymbol GetErrorResponseType( + in ApiControllerSymbolCache symbolCache, + IMethodSymbol method) + { + var errorTypeAttribute = + method.GetAttributes(symbolCache.ProducesErrorResponseTypeAttribute).FirstOrDefault() ?? + method.ContainingType.GetAttributes(symbolCache.ProducesErrorResponseTypeAttribute).FirstOrDefault() ?? + method.ContainingAssembly.GetAttributes(symbolCache.ProducesErrorResponseTypeAttribute).FirstOrDefault(); + + ITypeSymbol errorType = symbolCache.ProblemDetails; + if (errorTypeAttribute != null && + errorTypeAttribute.ConstructorArguments.Length == 1 && + errorTypeAttribute.ConstructorArguments[0].Kind == TypedConstantKind.Type && + errorTypeAttribute.ConstructorArguments[0].Value is ITypeSymbol typeSymbol) + { + errorType = typeSymbol; + } + + return errorType; + } + private static IList GetResponseMetadataFromConventions( - ApiControllerSymbolCache symbolCache, + in ApiControllerSymbolCache symbolCache, IMethodSymbol method, IReadOnlyList conventionTypes) { @@ -63,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return Array.Empty(); } - private static IMethodSymbol GetMethodFromConventionMethodAttribute(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + private static IMethodSymbol GetMethodFromConventionMethodAttribute(in ApiControllerSymbolCache symbolCache, IMethodSymbol method) { var attribute = method.GetAttributes(symbolCache.ApiConventionMethodAttribute, inherit: true) .FirstOrDefault(); @@ -97,7 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } private static IMethodSymbol MatchConventionMethod( - ApiControllerSymbolCache symbolCache, + in ApiControllerSymbolCache symbolCache, IMethodSymbol method, IReadOnlyList conventionTypes) { @@ -120,7 +137,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return null; } - private static IList GetResponseMetadataFromMethodAttributes(ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) + private static IList GetResponseMetadataFromMethodAttributes(in ApiControllerSymbolCache symbolCache, IMethodSymbol methodSymbol) { var metadataItems = new List(); var responseMetadataAttributes = methodSymbol.GetAttributes(symbolCache.ProducesResponseTypeAttribute, inherit: true); @@ -141,7 +158,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return metadataItems; } - internal static IReadOnlyList GetConventionTypes(ApiControllerSymbolCache symbolCache, IMethodSymbol method) + internal static IReadOnlyList GetConventionTypes(in ApiControllerSymbolCache symbolCache, IMethodSymbol method) { var attributes = method.ContainingType.GetAttributes(symbolCache.ApiConventionTypeAttribute).ToArray(); if (attributes.Length == 0) @@ -209,255 +226,5 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers return DefaultStatusCode; } - - internal static bool TryGetActualResponseMetadata( - in ApiControllerSymbolCache symbolCache, - SemanticModel semanticModel, - MethodDeclarationSyntax methodSyntax, - CancellationToken cancellationToken, - out IList actualResponseMetadata) - { - actualResponseMetadata = new List(); - - var allReturnStatementsReadable = true; - - foreach (var returnStatementSyntax in methodSyntax.DescendantNodes(_shouldDescendIntoChildren).OfType()) - { - if (returnStatementSyntax.IsMissing || returnStatementSyntax.Expression.IsMissing) - { - // Ignore malformed return statements. - continue; - } - - var responseMetadata = InspectReturnStatementSyntax( - symbolCache, - semanticModel, - returnStatementSyntax, - cancellationToken); - - if (responseMetadata != null) - { - actualResponseMetadata.Add(responseMetadata.Value); - } - else - { - allReturnStatementsReadable = false; - } - } - - return allReturnStatementsReadable; - } - - internal static ActualApiResponseMetadata? InspectReturnStatementSyntax( - in ApiControllerSymbolCache symbolCache, - SemanticModel semanticModel, - ReturnStatementSyntax returnStatementSyntax, - CancellationToken cancellationToken) - { - var returnExpression = returnStatementSyntax.Expression; - var typeInfo = semanticModel.GetTypeInfo(returnExpression, cancellationToken); - if (typeInfo.Type.TypeKind == TypeKind.Error) - { - return null; - } - - var statementReturnType = typeInfo.Type; - - var defaultStatusCodeAttribute = statementReturnType - .GetAttributes(symbolCache.DefaultStatusCodeAttribute, inherit: true) - .FirstOrDefault(); - - if (defaultStatusCodeAttribute != null) - { - var defaultStatusCode = GetDefaultStatusCode(defaultStatusCodeAttribute); - if (defaultStatusCode == null) - { - // Unable to read the status code even though the attribute exists. - return null; - } - - return new ActualApiResponseMetadata(returnStatementSyntax, defaultStatusCode.Value); - } - - if (!symbolCache.IActionResult.IsAssignableFrom(statementReturnType)) - { - // Return expression does not have a DefaultStatusCodeAttribute and it is not - // an instance of IActionResult. Must be returning the "model". - return new ActualApiResponseMetadata(returnStatementSyntax); - } - - int statusCode; - switch (returnExpression) - { - case InvocationExpressionSyntax invocation: - // Covers the 'return StatusCode(200)' case. - if (TryGetParameterStatusCode(symbolCache, semanticModel, invocation.Expression, invocation.ArgumentList, cancellationToken, out statusCode)) - { - return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); - } - break; - - case ObjectCreationExpressionSyntax creation: - // Covers the 'return new ObjectResult(...) { StatusCode = 200 }' case. - if (TryGetInitializerStatusCode(symbolCache, semanticModel, creation.Initializer, cancellationToken, out statusCode)) - { - return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); - } - - // Covers the 'return new StatusCodeResult(200) case. - if (TryGetParameterStatusCode(symbolCache, semanticModel, creation, creation.ArgumentList, cancellationToken, out statusCode)) - { - return new ActualApiResponseMetadata(returnStatementSyntax, statusCode); - } - break; - } - - return null; - } - - private static bool TryGetInitializerStatusCode( - in ApiControllerSymbolCache symbolCache, - SemanticModel semanticModel, - InitializerExpressionSyntax initializer, - CancellationToken cancellationToken, - out int statusCode) - { - if (initializer == null) - { - statusCode = default; - return false; - } - - for (var i = 0; i < initializer.Expressions.Count; i++) - { - if (!(initializer.Expressions[i] is AssignmentExpressionSyntax assignment)) - { - continue; - } - - if (assignment.Left is IdentifierNameSyntax identifier) - { - var symbolInfo = semanticModel.GetSymbolInfo(identifier, cancellationToken); - - if (symbolInfo.Symbol is IPropertySymbol property && IsInterfaceImplementation(property, symbolCache.StatusCodeActionResultStatusProperty)) - { - return TryGetExpressionStatusCode(semanticModel, assignment.Right, cancellationToken, out statusCode); - } - } - } - - statusCode = default; - return false; - } - - private static bool IsInterfaceImplementation(IPropertySymbol property, IPropertySymbol statusCodeActionResultStatusProperty) - { - if (property.Name != statusCodeActionResultStatusProperty.Name) - { - return false; - } - - for (var i = 0; i < property.ExplicitInterfaceImplementations.Length; i++) - { - if (property.ExplicitInterfaceImplementations[i] == statusCodeActionResultStatusProperty) - { - return true; - } - } - - var implementedProperty = property.ContainingType.FindImplementationForInterfaceMember(statusCodeActionResultStatusProperty); - return implementedProperty == property; - } - - private static bool TryGetParameterStatusCode( - in ApiControllerSymbolCache symbolCache, - SemanticModel semanticModel, - ExpressionSyntax expression, - BaseArgumentListSyntax argumentList, - CancellationToken cancellationToken, - out int statusCode) - { - var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); - - if (!(symbolInfo.Symbol is IMethodSymbol method)) - { - statusCode = default; - return false; - } - - for (var i = 0; i < method.Parameters.Length; i++) - { - var parameter = method.Parameters[i]; - if (!parameter.HasAttribute(symbolCache.StatusCodeValueAttribute)) - { - continue; - } - - - var argument = argumentList.Arguments[parameter.Ordinal]; - return TryGetExpressionStatusCode(semanticModel, argument.Expression, cancellationToken, out statusCode); - } - - statusCode = default; - return false; - } - - private static bool TryGetExpressionStatusCode( - SemanticModel semanticModel, - ExpressionSyntax expression, - CancellationToken cancellationToken, - out int statusCode) - { - if (expression is LiteralExpressionSyntax literal && literal.Token.Value is int literalStatusCode) - { - // Covers the 'return StatusCode(200)' case. - statusCode = literalStatusCode; - return true; - } - - if (expression is IdentifierNameSyntax || expression is MemberAccessExpressionSyntax) - { - var symbolInfo = semanticModel.GetSymbolInfo(expression, cancellationToken); - - if (symbolInfo.Symbol is IFieldSymbol field && field.HasConstantValue && field.ConstantValue is int constantStatusCode) - { - // Covers the 'return StatusCode(StatusCodes.Status200OK)' case. - // It also covers the 'return StatusCode(StatusCode)' case, where 'StatusCode' is a constant field. - statusCode = constantStatusCode; - return true; - } - - if (symbolInfo.Symbol is ILocalSymbol local && local.HasConstantValue && local.ConstantValue is int localStatusCode) - { - // Covers the 'return StatusCode(statusCode)' case, where 'statusCode' is a local constant. - statusCode = localStatusCode; - return true; - } - } - - statusCode = default; - return false; - } - - private static bool ShouldDescendIntoChildren(SyntaxNode syntaxNode) - { - return !syntaxNode.IsKind(SyntaxKind.LocalFunctionStatement) && - !syntaxNode.IsKind(SyntaxKind.ParenthesizedLambdaExpression) && - !syntaxNode.IsKind(SyntaxKind.SimpleLambdaExpression) && - !syntaxNode.IsKind(SyntaxKind.AnonymousMethodExpression); - } - - internal static int? GetDefaultStatusCode(AttributeData attribute) - { - if (attribute != null && - attribute.ConstructorArguments.Length == 1 && - attribute.ConstructorArguments[0].Kind == TypedConstantKind.Primitive && - attribute.ConstructorArguments[0].Value is int statusCode) - { - return statusCode; - } - - return null; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs index 291e25930a..3c392d3abe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc string actionName, string controllerName, object routeValues, - object value) + [ActionResultObjectValue] object value) : base(value) { ActionName = actionName; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs index 2fb29b505c..e571afe475 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The route data to use for generating the URL. /// The value to format in the entity body. - public AcceptedAtRouteResult(object routeValues, object value) + public AcceptedAtRouteResult(object routeValues, [ActionResultObjectValue] object value) : this(routeName: null, routeValues: routeValues, value: value) { } @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc public AcceptedAtRouteResult( string routeName, object routeValues, - object value) + [ActionResultObjectValue] object value) : base(value) { RouteName = routeName; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs index cf0d383fd3..0279285695 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The location at which the status of requested content can be monitored. /// The value to format in the entity body. - public AcceptedResult(string location, object value) + public AcceptedResult(string location, [ActionResultObjectValue] object value) : base(value) { Location = location; @@ -46,7 +46,7 @@ namespace Microsoft.AspNetCore.Mvc /// The location at which the status of requested content can be monitored /// It is an optional parameter and may be null /// The value to format in the entity body. - public AcceptedResult(Uri locationUri, object value) + public AcceptedResult(Uri locationUri, [ActionResultObjectValue] object value) : base(value) { if (locationUri == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs index 9af96ff7cc..cf3627fb31 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// Contains the errors to be returned to the client. - public BadRequestObjectResult(object error) + public BadRequestObjectResult([ActionResultObjectValue] object error) : base(error) { StatusCode = DefaultStatusCode; @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// containing the validation errors. - public BadRequestObjectResult(ModelStateDictionary modelState) + public BadRequestObjectResult([ActionResultObjectValue] ModelStateDictionary modelState) : base(new SerializableError(modelState)) { if (modelState == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs index 93927d78f9..696357783d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// Contains the errors to be returned to the client. - public ConflictObjectResult(object error) + public ConflictObjectResult([ActionResultObjectValue] object error) : base(error) { StatusCode = DefaultStatusCode; @@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// containing the validation errors. - public ConflictObjectResult(ModelStateDictionary modelState) + public ConflictObjectResult([ActionResultObjectValue] ModelStateDictionary modelState) : base(new SerializableError(modelState)) { if (modelState == null) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs index 520f8a5fbe..3cc780746a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs @@ -201,7 +201,7 @@ namespace Microsoft.AspNetCore.Mvc /// The status code to set on the response. /// The created object for the response. [NonAction] - public virtual StatusCodeResult StatusCode([StatusCodeValue] int statusCode) + public virtual StatusCodeResult StatusCode([ActionResultStatusCode] int statusCode) => new StatusCodeResult(statusCode); /// @@ -211,7 +211,7 @@ namespace Microsoft.AspNetCore.Mvc /// The value to set on the . /// The created object for the response. [NonAction] - public virtual ObjectResult StatusCode([StatusCodeValue] int statusCode, object value) + public virtual ObjectResult StatusCode([ActionResultStatusCode] int statusCode, [ActionResultObjectValue] object value) { var result = new ObjectResult(value) { @@ -304,9 +304,10 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual OkObjectResult Ok(object value) + public virtual OkObjectResult Ok([ActionResultObjectValue] object value) => new OkObjectResult(value); + #region RedirectResult variants /// /// Creates a object that redirects () /// to the specified . @@ -1077,7 +1078,9 @@ namespace Microsoft.AspNetCore.Mvc preserveMethod: true, fragment: fragment); } + #endregion + #region FileResult variants /// /// Returns a file with the specified as content (), /// and the specified as the Content-Type. @@ -1698,6 +1701,7 @@ namespace Microsoft.AspNetCore.Mvc EnableRangeProcessing = enableRangeProcessing, }; } + #endregion /// /// Creates an that produces an response. @@ -1712,7 +1716,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The created for the response. [NonAction] - public virtual UnauthorizedObjectResult Unauthorized(object value) + public virtual UnauthorizedObjectResult Unauthorized([ActionResultObjectValue] object value) => new UnauthorizedObjectResult(value); /// @@ -1728,7 +1732,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The created for the response. [NonAction] - public virtual NotFoundObjectResult NotFound(object value) + public virtual NotFoundObjectResult NotFound([ActionResultObjectValue] object value) => new NotFoundObjectResult(value); /// @@ -1745,7 +1749,7 @@ namespace Microsoft.AspNetCore.Mvc /// An error object to be returned to the client. /// The created for the response. [NonAction] - public virtual BadRequestObjectResult BadRequest(object error) + public virtual BadRequestObjectResult BadRequest([ActionResultObjectValue] object error) => new BadRequestObjectResult(error); /// @@ -1754,7 +1758,7 @@ namespace Microsoft.AspNetCore.Mvc /// The containing errors to be returned to the client. /// The created for the response. [NonAction] - public virtual BadRequestObjectResult BadRequest(ModelStateDictionary modelState) + public virtual BadRequestObjectResult BadRequest([ActionResultObjectValue] ModelStateDictionary modelState) { if (modelState == null) { @@ -1780,7 +1784,7 @@ namespace Microsoft.AspNetCore.Mvc /// An error object to be returned to the client. /// The created for the response. [NonAction] - public virtual UnprocessableEntityObjectResult UnprocessableEntity(object error) + public virtual UnprocessableEntityObjectResult UnprocessableEntity([ActionResultObjectValue] object error) { return new UnprocessableEntityObjectResult(error); } @@ -1791,7 +1795,7 @@ namespace Microsoft.AspNetCore.Mvc /// The containing errors to be returned to the client. /// The created for the response. [NonAction] - public virtual UnprocessableEntityObjectResult UnprocessableEntity(ModelStateDictionary modelState) + public virtual UnprocessableEntityObjectResult UnprocessableEntity([ActionResultObjectValue] ModelStateDictionary modelState) { if (modelState == null) { @@ -1815,7 +1819,7 @@ namespace Microsoft.AspNetCore.Mvc /// Contains errors to be returned to the client. /// The created for the response. [NonAction] - public virtual ConflictObjectResult Conflict(object error) + public virtual ConflictObjectResult Conflict([ActionResultObjectValue] object error) => new ConflictObjectResult(error); /// @@ -1824,7 +1828,7 @@ namespace Microsoft.AspNetCore.Mvc /// The containing errors to be returned to the client. /// The created for the response. [NonAction] - public virtual ConflictObjectResult Conflict(ModelStateDictionary modelState) + public virtual ConflictObjectResult Conflict([ActionResultObjectValue] ModelStateDictionary modelState) => new ConflictObjectResult(modelState); /// @@ -1832,7 +1836,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The created for the response. [NonAction] - public virtual ActionResult ValidationProblem(ValidationProblemDetails descriptor) + public virtual ActionResult ValidationProblem([ActionResultObjectValue] ValidationProblemDetails descriptor) { if (descriptor == null) { @@ -1847,7 +1851,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The created for the response. [NonAction] - public virtual ActionResult ValidationProblem(ModelStateDictionary modelStateDictionary) + public virtual ActionResult ValidationProblem([ActionResultObjectValue] ModelStateDictionary modelStateDictionary) { if (modelStateDictionary == null) { @@ -1877,7 +1881,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedResult Created(string uri, object value) + public virtual CreatedResult Created(string uri, [ActionResultObjectValue] object value) { if (uri == null) { @@ -1894,7 +1898,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedResult Created(Uri uri, object value) + public virtual CreatedResult Created(Uri uri, [ActionResultObjectValue] object value) { if (uri == null) { @@ -1911,7 +1915,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedAtActionResult CreatedAtAction(string actionName, object value) + public virtual CreatedAtActionResult CreatedAtAction(string actionName, [ActionResultObjectValue] object value) => CreatedAtAction(actionName, routeValues: null, value: value); /// @@ -1922,7 +1926,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, object value) + public virtual CreatedAtActionResult CreatedAtAction(string actionName, object routeValues, [ActionResultObjectValue] object value) => CreatedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value); /// @@ -1938,7 +1942,7 @@ namespace Microsoft.AspNetCore.Mvc string actionName, string controllerName, object routeValues, - object value) + [ActionResultObjectValue] object value) => new CreatedAtActionResult(actionName, controllerName, routeValues, value); /// @@ -1948,7 +1952,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object value) + public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, [ActionResultObjectValue] object value) => CreatedAtRoute(routeName, routeValues: null, value: value); /// @@ -1958,7 +1962,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, object value) + public virtual CreatedAtRouteResult CreatedAtRoute(object routeValues, [ActionResultObjectValue] object value) => CreatedAtRoute(routeName: null, routeValues: routeValues, value: value); /// @@ -1969,7 +1973,7 @@ namespace Microsoft.AspNetCore.Mvc /// The content value to format in the entity body. /// The created for the response. [NonAction] - public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues, object value) + public virtual CreatedAtRouteResult CreatedAtRoute(string routeName, object routeValues, [ActionResultObjectValue] object value) => new CreatedAtRouteResult(routeName, routeValues, value); /// @@ -1986,7 +1990,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedResult Accepted(object value) + public virtual AcceptedResult Accepted([ActionResultObjectValue] object value) => new AcceptedResult(location: null, value: value); /// @@ -2023,7 +2027,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedResult Accepted(string uri, object value) + public virtual AcceptedResult Accepted(string uri, [ActionResultObjectValue] object value) => new AcceptedResult(uri, value); /// @@ -2033,7 +2037,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedResult Accepted(Uri uri, object value) + public virtual AcceptedResult Accepted(Uri uri, [ActionResultObjectValue] object value) { if (uri == null) { @@ -2069,7 +2073,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, object value) + public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, [ActionResultObjectValue] object value) => AcceptedAtAction(actionName, routeValues: null, value: value); /// @@ -2080,7 +2084,7 @@ namespace Microsoft.AspNetCore.Mvc /// The route data to use for generating the URL. /// The created for the response. [NonAction] - public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName, object routeValues) + public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, string controllerName, [ActionResultObjectValue] object routeValues) => AcceptedAtAction(actionName, controllerName, routeValues, value: null); /// @@ -2091,7 +2095,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, object routeValues, object value) + public virtual AcceptedAtActionResult AcceptedAtAction(string actionName, object routeValues, [ActionResultObjectValue] object value) => AcceptedAtAction(actionName, controllerName: null, routeValues: routeValues, value: value); /// @@ -2107,7 +2111,7 @@ namespace Microsoft.AspNetCore.Mvc string actionName, string controllerName, object routeValues, - object value) + [ActionResultObjectValue] object value) => new AcceptedAtActionResult(actionName, controllerName, routeValues, value); /// @@ -2116,7 +2120,7 @@ namespace Microsoft.AspNetCore.Mvc /// The route data to use for generating the URL. /// The created for the response. [NonAction] - public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues) + public virtual AcceptedAtRouteResult AcceptedAtRoute([ActionResultObjectValue] object routeValues) => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: null); /// @@ -2145,7 +2149,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues, object value) + public virtual AcceptedAtRouteResult AcceptedAtRoute(object routeValues, [ActionResultObjectValue] object value) => AcceptedAtRoute(routeName: null, routeValues: routeValues, value: value); /// @@ -2156,7 +2160,7 @@ namespace Microsoft.AspNetCore.Mvc /// The optional content value to format in the entity body; may be null. /// The created for the response. [NonAction] - public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName, object routeValues, object value) + public virtual AcceptedAtRouteResult AcceptedAtRoute(string routeName, object routeValues, [ActionResultObjectValue] object value) => new AcceptedAtRouteResult(routeName, routeValues, value); /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs index f173c2ba9d..a43516bde6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs @@ -32,7 +32,7 @@ namespace Microsoft.AspNetCore.Mvc string actionName, string controllerName, object routeValues, - object value) + [ActionResultObjectValue] object value) : base(value) { ActionName = actionName; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs index ad05b6e4a9..324f872692 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// The route data to use for generating the URL. /// The value to format in the entity body. - public CreatedAtRouteResult(object routeValues, object value) + public CreatedAtRouteResult(object routeValues, [ActionResultObjectValue] object value) : this(routeName: null, routeValues: routeValues, value: value) { } @@ -41,7 +41,7 @@ namespace Microsoft.AspNetCore.Mvc public CreatedAtRouteResult( string routeName, object routeValues, - object value) + [ActionResultObjectValue] object value) : base(value) { RouteName = routeName; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs new file mode 100644 index 0000000000..8a87c33b2b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs @@ -0,0 +1,27 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Attribute annoted on ActionResult constructor, helper method parameters, and properties to indicate + /// that the parameter or property is used to set the "value" for ActionResult. + /// + /// Analyzers match this parameter by type name. This allows users to annotate custom results \ custom helpers + /// with a user defined attribute without having to expose this type. + /// + /// + /// This attribute is intentionally marked Inherited=false since the analyzer does not walk the inheritance graph. + /// + /// + /// + /// BadObjectResult([ActionResultObjectValueAttribute] object value) + /// ObjectResult { [ActionResultObjectValueAttribute] public object Value { get; set; } } + /// + [AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property, AllowMultiple = false, Inherited = false)] + internal sealed class ActionResultObjectValueAttribute : Attribute + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs new file mode 100644 index 0000000000..2b4d1d4119 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.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; + +namespace Microsoft.AspNetCore.Mvc.Infrastructure +{ + /// + /// Attribute annoted on ActionResult constructor and helper method parameters to indicate + /// that the parameter is used to set the "statusCode" for the ActionResult. + /// + /// Analyzers match this parameter by type name. This allows users to annotate custom results \ custom helpers + /// with a user defined attribute without having to expose this type. + /// + /// + /// This attribute is intentionally marked Inherited=false since the analyzer does not walk the inheritance graph. + /// + /// + /// + /// StatusCodeResult([ActionResultStatusCodeParameter] int statusCode) + /// + [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] + internal sealed class ActionResultStatusCodeAttribute : Attribute + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs deleted file mode 100644 index 944f958d62..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/StatusCodeValueAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Mvc.Infrastructure -{ - [AttributeUsage(AttributeTargets.Parameter, AllowMultiple = false, Inherited = false)] - internal sealed class StatusCodeValueAttribute : Attribute - { - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs index c8856473aa..11bb6d8732 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// The value to format in the entity body. - public NotFoundObjectResult(object value) + public NotFoundObjectResult([ActionResultObjectValue] object value) : base(value) { StatusCode = DefaultStatusCode; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs index e81dbccfa1..bb7fc0522d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs @@ -18,6 +18,7 @@ namespace Microsoft.AspNetCore.Mvc ContentTypes = new MediaTypeCollection(); } + [ActionResultObjectValue] public object Value { get; set; } public FormatterCollection Formatters { get; set; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs index c95b69237e..70de9cdafe 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs @@ -20,7 +20,7 @@ namespace Microsoft.AspNetCore.Mvc /// with the given . /// /// The HTTP status code of the response. - public StatusCodeResult([StatusCodeValue] int statusCode) + public StatusCodeResult([ActionResultStatusCode] int statusCode) { StatusCode = statusCode; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs index ebab2df60f..f4a9a4556f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Creates a new instance. /// - public UnauthorizedObjectResult(object value) : base(value) + public UnauthorizedObjectResult([ActionResultObjectValue] object value) : base(value) { StatusCode = DefaultStatusCode; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs index 85c21a9594..dcee40ef05 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs @@ -19,7 +19,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// containing the validation errors. - public UnprocessableEntityObjectResult(ModelStateDictionary modelState) + public UnprocessableEntityObjectResult([ActionResultObjectValue] ModelStateDictionary modelState) : this(new SerializableError(modelState)) { } @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc /// Creates a new instance. /// /// Contains errors to be returned to the client. - public UnprocessableEntityObjectResult(object error) + public UnprocessableEntityObjectResult([ActionResultObjectValue] object error) : base(error) { StatusCode = DefaultStatusCode; diff --git a/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs b/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs new file mode 100644 index 0000000000..bf4a12fd67 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs @@ -0,0 +1,369 @@ +// 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.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Analyzer.Testing; +using Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ActualApiResponseMetadataFactoryTest; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + public class ActualApiResponseMetadataFactoryTest + { + private static readonly string Namespace = typeof(ActualApiResponseMetadataFactoryTest).Namespace; + + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingStatusCodeConstants() + { + // Arrange + var compilation = await GetCompilation("GetDefaultStatusCodeTest"); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingStatusCodesConstants).FullName).GetAttributes()[0]; + + // Act + var actual = ActualApiResponseMetadataFactory.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(412, actual); + } + + [Fact] + public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingHttpStatusCast() + { + // Arrange + var compilation = await GetCompilation("GetDefaultStatusCodeTest"); + var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingHttpStatusCodeCast).FullName).GetAttributes()[0]; + + // Act + var actual = ActualApiResponseMetadataFactory.GetDefaultStatusCode(attribute); + + // Assert + Assert.Equal(302, actual); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound() + { + // Arrange & Act + var source = @" + using Microsoft.AspNetCore.Mvc; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers +{ + [ApiController] + public class TestController : ControllerBase + { + public IActionResult Get(int id) + { + return new DoesNotExist(id); + } + } +}"; + var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); + var compilation = await project.GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var returnType = compilation.GetTypeByMetadataName($"{Namespace}.TestController"); + var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree; + + var method = (IMethodSymbol)returnType.GetMembers().First(); + var methodSyntax = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var returnStatement = methodSyntax.DescendantNodes().OfType().First(); + + var actualResponseMetadata = ActualApiResponseMetadataFactory.InspectReturnStatementSyntax( + symbolCache, + compilation.GetSemanticModel(syntaxTree), + returnStatement, + CancellationToken.None); + + // Assert + Assert.Null(actualResponseMetadata); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(401, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.True(actualResponseMetadata.Value.IsDefaultResponse); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsStatusCodeFromStatusCodePropertyAssignment() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(201, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsStatusCodeFromConstructorAssignment() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(204, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsStatusCodeFromHelperMethod() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(302, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_UsesExplicitlySpecifiedStatusCode_ForActionResultWithDefaultStatusCode() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(422, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_ReadsStatusCodeConstant() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(423, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_DoesNotReadLocalFieldWithConstantValue() + { + // This is a gap in the analyzer. We're using this to document the current behavior and not an expecation. + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.Null(actualResponseMetadata); + } + + [Fact] + public async Task InspectReturnExpression_FallsBackToDefaultStatusCode_WhenAppliedStatusCodeCannotBeRead() + { + // This is a gap in the analyzer. We're using this to document the current behavior and not an expecation. + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Equal(400, actualResponseMetadata.Value.StatusCode); + } + + [Fact] + public async Task InspectReturnExpression_SetsReturnType_WhenLiteralTypeIsSpecifiedInConstructor() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata?.ReturnType); + Assert.Equal("TestModel", actualResponseMetadata.Value.ReturnType.Name); + } + + [Fact] + public async Task InspectReturnExpression_SetsReturnType_WhenLocalValueIsSpecifiedInConstructor() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata?.ReturnType); + Assert.Equal("TestModel", actualResponseMetadata.Value.ReturnType.Name); + } + + [Fact] + public async Task InspectReturnExpression_SetsReturnType_WhenValueIsReturned() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata?.ReturnType); + Assert.Equal("TestModel", actualResponseMetadata.Value.ReturnType.Name); + } + + [Fact] + public async Task InspectReturnExpression_ReturnsNullReturnType_IfValueIsNotSpecified() + { + // Arrange & Act + var actualResponseMetadata = await RunInspectReturnStatementSyntax(); + + // Assert + Assert.NotNull(actualResponseMetadata); + Assert.Null(actualResponseMetadata.Value.ReturnType); + } + + [Fact] + public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningOkResult() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningOkResult); + + // Act + var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.False(metadata.IsDefaultResponse); + Assert.Equal(200, metadata.StatusCode); + }); + } + + [Fact] + public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningModel() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningModel); + + // Act + var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.True(metadata.IsDefaultResponse); + }); + } + + [Fact] + public async Task TryGetActualResponseMetadata_ActionReturningNotFoundAndModel() + { + // Arrange + var typeName = typeof(TryGetActualResponseMetadataController).FullName; + var methodName = nameof(TryGetActualResponseMetadataController.ActionReturningNotFoundAndModel); + + // Act + var (success, responseMetadatas, testSource) = await TryGetActualResponseMetadata(typeName, methodName); + + // Assert + Assert.True(success); + Assert.Collection( + responseMetadatas, + metadata => + { + Assert.False(metadata.IsDefaultResponse); + Assert.Equal(204, metadata.StatusCode); + AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM1"], metadata.ReturnStatement.GetLocation()); + + }, + metadata => + { + Assert.True(metadata.IsDefaultResponse); + AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM2"], metadata.ReturnStatement.GetLocation()); + }); + } + + private async Task<(bool result, IList responseMetadatas, TestSource testSource)> TryGetActualResponseMetadata(string typeName, string methodName) + { + var testSource = MvcTestSource.Read(GetType().Name, "TryGetActualResponseMetadataTests"); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + var compilation = await GetCompilation("TryGetActualResponseMetadataTests"); + + var type = compilation.GetTypeByMetadataName(typeName); + var method = (IMethodSymbol)type.GetMembers(methodName).First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var syntaxTree = method.DeclaringSyntaxReferences[0].SyntaxTree; + var methodSyntax = (MethodDeclarationSyntax)syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var semanticModel = compilation.GetSemanticModel(syntaxTree); + + var result = ActualApiResponseMetadataFactory.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, CancellationToken.None, out var responseMetadatas); + + return (result, responseMetadatas, testSource); + } + + private async Task RunInspectReturnStatementSyntax([CallerMemberName]string test = null) + { + // Arrange + var compilation = await GetCompilation("InspectReturnExpressionTests"); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var controllerType = compilation.GetTypeByMetadataName(typeof(TestFiles.InspectReturnExpressionTests.TestController).FullName); + var syntaxTree = controllerType.DeclaringSyntaxReferences[0].SyntaxTree; + + var method = (IMethodSymbol)Assert.Single(controllerType.GetMembers(test)); + var methodSyntax = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var returnStatement = methodSyntax.DescendantNodes().OfType().First(); + + return ActualApiResponseMetadataFactory.InspectReturnStatementSyntax( + symbolCache, + compilation.GetSemanticModel(syntaxTree), + returnStatement, + CancellationToken.None); + } + + private async Task RunInspectReturnStatementSyntax(string source, string test) + { + var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); + var compilation = await project.GetCompilationAsync(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}"); + var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree; + + var method = (IMethodSymbol)returnType.GetMembers().First(); + var methodSyntax = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); + var returnStatement = methodSyntax.DescendantNodes().OfType().First(); + + return ActualApiResponseMetadataFactory.InspectReturnStatementSyntax( + symbolCache, + compilation.GetSemanticModel(syntaxTree), + returnStatement, + CancellationToken.None); + } + + private Task GetCompilation(string test) + { + var testSource = MvcTestSource.Read(GetType().Name, test); + var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); + + return project.GetCompilationAsync(); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs index 755fe9b136..f0cfbff1e5 100644 --- a/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs @@ -21,6 +21,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task CodeFixAddsMissingStatusCodes() => RunTest(); + [Fact] + public Task CodeFixAddsMissingStatusCodesAndTypes() => RunTest(); + [Fact] public Task CodeFixWithConventionAddsMissingStatusCodes() => RunTest(); @@ -36,6 +39,9 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [Fact] public Task CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants() => RunTest(); + [Fact] + public Task CodeFixAddsResponseTypeWhenDifferentFromErrorType() => RunTest(); + [Fact] public Task CodeFixAddsStatusCodesFromMethodParameters() => RunTest(); diff --git a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs index 5e68cdbe54..77c5b0d751 100644 --- a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs +++ b/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs @@ -42,6 +42,71 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers public Task NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions() => RunNoDiagnosticsAreReturned(); + [Fact] + public async Task DiagnosticsAreReturned_ForIncompleteActionResults() + { + // Arrange + var source = @" +using Microsoft.AspNetCore.Mvc; + +namespace Test +{ + [ApiController] + [Route(""[controller]/[action]"") + public class TestController : ControllerBase + { + public IActionResult Get(int id) + { + if (id == 0) + { + /*MM*/return NotFound(); + } + + return; + } + } +}"; + var testSource = TestSource.Read(source); + var expectedLocation = testSource.DefaultMarkerLocation; + + // Act + var result = await Executor.GetDiagnosticsAsync(testSource.Source); + + // Assert + var diagnostic = Assert.Single(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id); + AnalyzerAssert.DiagnosticLocation(expectedLocation, diagnostic.Location); + } + + [Fact] + public async Task NoDiagnosticsAreReturned_WhenActionDoesNotCompile() + { + // Arrange + var source = @" +namespace Test +{ + [ApiController] + [Route(""[controller]/[action]"") + public class TestController : ControllerBase + { + public IActionResult Get(int id) + { + if (id == 0) + { + return NotFound(); + } + + return Ok(); + } + } +}"; + + // Act + var result = await Executor.GetDiagnosticsAsync(source); + + // Assert + Assert.DoesNotContain(result, d => d.Id == ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode.Id); + } + [Fact] public Task DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode() => RunTest(ApiDiagnosticDescriptors.API1000_ActionReturnsUndocumentedStatusCode, 404); diff --git a/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs b/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs index fcfc65ebef..eee4a862f3 100644 --- a/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs +++ b/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs @@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ImplicitResponse; - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -35,7 +35,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ImplicitResponse; - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -49,7 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(200, AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -67,7 +67,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -85,7 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(201, AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 200, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -99,7 +99,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesResponseType(302, AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 302); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 302, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -116,7 +116,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, actualStatusCode); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, actualStatusCode, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -130,7 +130,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 204); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, 204, null); // Act var matches = declaredMetadata.Matches(actualMetadata); @@ -144,7 +144,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers { // Arrange var declaredMetadata = DeclaredApiResponseMetadata.ForProducesDefaultResponse(AttributeData, Mock.Of()); - var actualMetadata = new ActualApiResponseMetadata(ReturnStatement); + var actualMetadata = new ActualApiResponseMetadata(ReturnStatement, null); // Act var matches = declaredMetadata.Matches(actualMetadata); diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs index c417a199fe..57d823e8c6 100644 --- a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs +++ b/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs @@ -1,15 +1,11 @@ // 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.Runtime.CompilerServices; -using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Analyzer.Testing; using Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest; using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp.Syntax; using Xunit; namespace Microsoft.AspNetCore.Mvc.Api.Analyzers @@ -218,12 +214,12 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public async Task GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod() + public async Task GetResponseMetadata_WithProducesResponseTypeAndApiConventionMethod() { // Arrange var compilation = await GetResponseMetadataCompilation(); var controller = compilation.GetTypeByMetadataName($"{Namespace}.{nameof(GetResponseMetadata_ControllerActionWithAttributes)}"); - var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod)).First(); + var method = (IMethodSymbol)controller.GetMembers(nameof(GetResponseMetadata_ControllerActionWithAttributes.GetResponseMetadata_WithProducesResponseTypeAndApiConventionMethod)).First(); var symbolCache = new ApiControllerSymbolCache(compilation); // Act @@ -362,193 +358,75 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers } [Fact] - public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingStatusCodeConstants() + public async Task GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered() { // Arrange - var compilation = await GetCompilation("GetDefaultStatusCodeTest"); - var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingStatusCodesConstants).FullName).GetAttributes()[0]; + var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered)); + var expected = compilation.GetTypeByMetadataName(typeof(ProblemDetails).FullName); - // Act - var actual = SymbolApiResponseMetadataProvider.GetDefaultStatusCode(attribute); - - // Assert - Assert.Equal(412, actual); - } - - [Fact] - public async Task GetDefaultStatusCode_ReturnsValueDefinedUsingHttpStatusCast() - { - // Arrange - var compilation = await GetCompilation("GetDefaultStatusCodeTest"); - var attribute = compilation.GetTypeByMetadataName(typeof(TestActionResultUsingHttpStatusCodeCast).FullName).GetAttributes()[0]; - - // Act - var actual = SymbolApiResponseMetadataProvider.GetDefaultStatusCode(attribute); - - // Assert - Assert.Equal(302, actual); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound() - { - // Arrange & Act - var source = @" - using Microsoft.AspNetCore.Mvc; - -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers -{ - [ApiController] - public class InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound : ControllerBase - { - public IActionResult Get(int id) - { - return new DoesNotExist(id); - } - } -}"; - var actualResponseMetadata = await RunInspectReturnStatementSyntax(source, nameof(InspectReturnExpression_ReturnsNull_IfReturnExpressionCannotBeFound)); - - // Assert - Assert.Null(actualResponseMetadata); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult() - { - // Arrange & Act - var actualResponseMetadata = await RunInspectReturnStatementSyntax(); - - // Assert - Assert.NotNull(actualResponseMetadata); - Assert.Equal(401, actualResponseMetadata.Value.StatusCode); - } - - [Fact] - public async Task InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult() - { - // Arrange & Act - var actualResponseMetadata = await RunInspectReturnStatementSyntax(); - - // Assert - Assert.NotNull(actualResponseMetadata); - Assert.True(actualResponseMetadata.Value.IsDefaultResponse); - } - - [Fact] - public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningOkResult() - { - // Arrange - var typeName = typeof(TryGetActualResponseMetadataController).FullName; - var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningOkResult); - - // Act - var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); - - // Assert - Assert.True(success); - Assert.Collection( - responseMetadatas, - metadata => - { - Assert.False(metadata.IsDefaultResponse); - Assert.Equal(200, metadata.StatusCode); - }); - } - - [Fact] - public async Task TryGetActualResponseMetadata_ActionWithActionResultOfTReturningModel() - { - // Arrange - var typeName = typeof(TryGetActualResponseMetadataController).FullName; - var methodName = nameof(TryGetActualResponseMetadataController.ActionWithActionResultOfTReturningModel); - - // Act - var (success, responseMetadatas, _) = await TryGetActualResponseMetadata(typeName, methodName); - - // Assert - Assert.True(success); - Assert.Collection( - responseMetadatas, - metadata => - { - Assert.True(metadata.IsDefaultResponse); - }); - } - - [Fact] - public async Task TryGetActualResponseMetadata_ActionReturningNotFoundAndModel() - { - // Arrange - var typeName = typeof(TryGetActualResponseMetadataController).FullName; - var methodName = nameof(TryGetActualResponseMetadataController.ActionReturningNotFoundAndModel); - - // Act - var (success, responseMetadatas, testSource) = await TryGetActualResponseMetadata(typeName, methodName); - - // Assert - Assert.True(success); - Assert.Collection( - responseMetadatas, - metadata => - { - Assert.False(metadata.IsDefaultResponse); - Assert.Equal(204, metadata.StatusCode); - AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM1"], metadata.ReturnStatement.GetLocation()); - - }, - metadata => - { - Assert.True(metadata.IsDefaultResponse); - AnalyzerAssert.DiagnosticLocation(testSource.MarkerLocations["MM2"], metadata.ReturnStatement.GetLocation()); - }); - } - - private async Task<(bool result, IList responseMetadatas, TestSource testSource)> TryGetActualResponseMetadata(string typeName, string methodName) - { - var testSource = MvcTestSource.Read(GetType().Name, "TryGetActualResponseMetadataTests"); - var project = DiagnosticProject.Create(GetType().Assembly, new[] { testSource.Source }); - - var compilation = await GetCompilation("TryGetActualResponseMetadataTests"); - - var type = compilation.GetTypeByMetadataName(typeName); - var method = (IMethodSymbol)type.GetMembers(methodName).First(); + var type = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscoveredController).FullName); + var method = (IMethodSymbol)type.GetMembers("Action").First(); var symbolCache = new ApiControllerSymbolCache(compilation); - var syntaxTree = method.DeclaringSyntaxReferences[0].SyntaxTree; - var methodSyntax = (MethodDeclarationSyntax)syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); - var semanticModel = compilation.GetSemanticModel(syntaxTree); + // Act + var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method); - var result = SymbolApiResponseMetadataProvider.TryGetActualResponseMetadata(symbolCache, semanticModel, methodSyntax, CancellationToken.None, out var responseMetadatas); - - return (result, responseMetadatas, testSource); + // Assert + Assert.Same(expected, result); } - private async Task RunInspectReturnStatementSyntax([CallerMemberName]string test = null) + [Fact] + public async Task GetErrorResponseType_ReturnsTypeDefinedAtAssembly() { // Arrange - var testSource = MvcTestSource.Read(GetType().Name, test); - return await RunInspectReturnStatementSyntax(testSource.Source, test); - } + var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtAssembly)); + var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtAssemblyModel).FullName); - private async Task RunInspectReturnStatementSyntax(string source, string test) - { - var project = DiagnosticProject.Create(GetType().Assembly, new[] { source }); - var compilation = await project.GetCompilationAsync(); + var type = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtAssemblyController).FullName); + var method = (IMethodSymbol)type.GetMembers("Action").First(); var symbolCache = new ApiControllerSymbolCache(compilation); - var returnType = compilation.GetTypeByMetadataName($"{Namespace}.{test}"); - var syntaxTree = returnType.DeclaringSyntaxReferences[0].SyntaxTree; + // Act + var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method); - var method = (IMethodSymbol)returnType.GetMembers().First(); - var methodSyntax = syntaxTree.GetRoot().FindNode(method.Locations[0].SourceSpan); - var returnStatement = methodSyntax.DescendantNodes().OfType().First(); + // Assert + Assert.Same(expected, result); + } - return SymbolApiResponseMetadataProvider.InspectReturnStatementSyntax( - symbolCache, - compilation.GetSemanticModel(syntaxTree), - returnStatement, - CancellationToken.None); + [Fact] + public async Task GetErrorResponseType_ReturnsTypeDefinedAtController() + { + // Arrange + var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtController)); + var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtControllerModel).FullName); + + var type = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtControllerController).FullName); + var method = (IMethodSymbol)type.GetMembers("Action").First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method); + + // Assert + Assert.Same(expected, result); + } + + [Fact] + public async Task GetErrorResponseType_ReturnsTypeDefinedAtAction() + { + // Arrange + var compilation = await GetCompilation(nameof(GetErrorResponseType_ReturnsTypeDefinedAtAction)); + var expected = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionModel).FullName); + + var type = compilation.GetTypeByMetadataName(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionController).FullName); + var method = (IMethodSymbol)type.GetMembers("Action").First(); + var symbolCache = new ApiControllerSymbolCache(compilation); + + // Act + var result = SymbolApiResponseMetadataProvider.GetErrorResponseType(symbolCache, method); + + // Assert + Assert.Same(expected, result); } private Task GetResponseMetadataCompilation() => GetCompilation("GetResponseMetadataTests"); diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetDefaultStatusCodeTest.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs new file mode 100644 index 0000000000..c7e12719f4 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs @@ -0,0 +1,81 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.InspectReturnExpressionTests +{ + public class TestController : ControllerBase + { + public object InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult() + { + return new TestModel(); + } + + public IActionResult InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult() + { + return Unauthorized(); + } + + public IActionResult InspectReturnExpression_ReturnsStatusCodeFromStatusCodePropertyAssignment() + { + return new ObjectResult(new object()) { StatusCode = 201 }; + } + + public IActionResult InspectReturnExpression_ReturnsStatusCodeFromConstructorAssignment() + { + return new StatusCodeResult(204); + } + + public IActionResult InspectReturnExpression_ReturnsStatusCodeFromHelperMethod() + { + return StatusCode(302); + } + + public IActionResult InspectReturnExpression_UsesExplicitlySpecifiedStatusCode_ForActionResultWithDefaultStatusCode() + { + return new BadRequestObjectResult(new object()) + { + StatusCode = StatusCodes.Status422UnprocessableEntity, + }; + } + + public IActionResult InspectReturnExpression_ReadsStatusCodeConstant() + { + return StatusCode(StatusCodes.Status423Locked); + } + + public IActionResult InspectReturnExpression_DoesNotReadLocalFieldWithConstantValue() + { + var statusCode = StatusCodes.Status429TooManyRequests; + return StatusCode(statusCode); + } + + public IActionResult InspectReturnExpression_FallsBackToDefaultStatusCode_WhenAppliedStatusCodeCannotBeRead() + { + var statusCode = StatusCodes.Status422UnprocessableEntity; + return new BadRequestObjectResult(new object()) { StatusCode = statusCode }; + } + + public IActionResult InspectReturnExpression_SetsReturnType_WhenLiteralTypeIsSpecifiedInConstructor() + { + return new BadRequestObjectResult(new TestModel()); + } + + public IActionResult InspectReturnExpression_SetsReturnType_WhenLocalValueIsSpecifiedInConstructor() + { + var local = new TestModel(); + return new BadRequestObjectResult(local); + } + + public IActionResult InspectReturnExpression_ReturnsNullReturnType_IfValueIsNotSpecified() + { + return NotFound(); + } + + public ActionResult InspectReturnExpression_SetsReturnType_WhenValueIsReturned() + { + var local = new TestModel(); + return local; + } + } + + public class TestModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs similarity index 92% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs rename to test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs index 38e4bc1a81..389b605adb 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/TryGetActualResponseMetadataTests.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.ActualApiResponseMetadataFactoryTest { public class TryGetActualResponseMetadataController : ControllerBase { diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs new file mode 100644 index 0000000000..43f07eca63 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs @@ -0,0 +1,25 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsMissingStatusCodesAndTypes : ControllerBase + { + [ProducesResponseType(StatusCodes.Status404NotFound)] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(ModelState); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs new file mode 100644 index 0000000000..a758564bcf --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs @@ -0,0 +1,28 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsMissingStatusCodesAndTypes : ControllerBase + { + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ModelBinding.ModelStateDictionary), StatusCodes.Status400BadRequest)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(); + } + + if (id == 1) + { + return BadRequest(ModelState); + } + + return Ok(new object()); + } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs new file mode 100644 index 0000000000..02d2e72efb --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs @@ -0,0 +1,26 @@ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._INPUT_ +{ + [ProducesErrorResponseType(typeof(CodeFixAddsResponseTypeWhenDifferentErrorModel))] + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsResponseTypeWhenDifferentFromErrorType : ControllerBase + { + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(new CodeFixAddsResponseTypeWhenDifferentErrorModel()); + } + + if (id == 1) + { + var validationProblemDetails = new ValidationProblemDetails(ModelState); + return BadRequest(validationProblemDetails); + } + + return Ok(new object()); + } + } + + public class CodeFixAddsResponseTypeWhenDifferentErrorModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs new file mode 100644 index 0000000000..f6be4784e5 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Http; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers._OUTPUT_ +{ + [ProducesErrorResponseType(typeof(CodeFixAddsResponseTypeWhenDifferentErrorModel))] + [ApiController] + [Route("[controller]/[action]")] + public class CodeFixAddsResponseTypeWhenDifferentFromErrorType : ControllerBase + { + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType(typeof(ValidationProblemDetails), StatusCodes.Status400BadRequest)] + [ProducesResponseType(StatusCodes.Status404NotFound)] + [ProducesDefaultResponseType] + public IActionResult GetItem(int id) + { + if (id == 0) + { + return NotFound(new CodeFixAddsResponseTypeWhenDifferentErrorModel()); + } + + if (id == 1) + { + var validationProblemDetails = new ValidationProblemDetails(ModelState); + return BadRequest(validationProblemDetails); + } + + return Ok(new object()); + } + } + + public class CodeFixAddsResponseTypeWhenDifferentErrorModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs new file mode 100644 index 0000000000..7e7834edbe --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs @@ -0,0 +1,7 @@ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +{ + public class GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscoveredController + { + public void Action() { } + } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs new file mode 100644 index 0000000000..9e8f37d0c6 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +{ + [ProducesErrorResponseType(typeof(ModelStateDictionary))] + public class GetErrorResponseType_ReturnsTypeDefinedAtActionController + { + [ProducesErrorResponseType(typeof(GetErrorResponseType_ReturnsTypeDefinedAtActionModel))] + public void Action() { } + } + + public class GetErrorResponseType_ReturnsTypeDefinedAtActionModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs new file mode 100644 index 0000000000..b5199490d3 --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; + +[assembly: ProducesErrorResponseType(typeof(Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest.GetErrorResponseType_ReturnsTypeDefinedAtAssemblyModel))] + +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +{ + public class GetErrorResponseType_ReturnsTypeDefinedAtAssemblyController + { + public void Action() { } + } + + public class GetErrorResponseType_ReturnsTypeDefinedAtAssemblyModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs new file mode 100644 index 0000000000..b731f3f0eb --- /dev/null +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs @@ -0,0 +1,10 @@ +namespace Microsoft.AspNetCore.Mvc.Api.Analyzers.TestFiles.SymbolApiResponseMetadataProviderTest +{ + [ProducesErrorResponseType(typeof(GetErrorResponseType_ReturnsTypeDefinedAtControllerModel))] + public class GetErrorResponseType_ReturnsTypeDefinedAtControllerController + { + public void Action() { } + } + + public class GetErrorResponseType_ReturnsTypeDefinedAtControllerModel { } +} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs index 9040a07256..389617609b 100644 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs +++ b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs @@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Mvc.Api.Analyzers [ProducesResponseType(204)] [ApiConventionMethod(typeof(DefaultApiConventions), nameof(DefaultApiConventions.Find))] - public IActionResult GetResponseMetadata_WIthProducesResponseTypeAndApiConventionMethod() => null; + public IActionResult GetResponseMetadata_WithProducesResponseTypeAndApiConventionMethod() => null; } public class Person { } diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs deleted file mode 100644 index 02a1ffb40a..0000000000 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers -{ - public class InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResult : ControllerBase - { - public object Get() - { - return new InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResultModel(); - } - } - - public class InspectReturnExpression_ReturnsDefaultResponseMetadata_IfReturnedTypeIsNotActionResultModel { } -} diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs b/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs deleted file mode 100644 index 94d66474f5..0000000000 --- a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Microsoft.AspNetCore.Mvc.Api.Analyzers -{ - public class InspectReturnExpression_ReturnsStatusCodeFromDefaultStatusCodeAttributeOnActionResult : ControllerBase - { - public IActionResult Get() - { - return Unauthorized(); - } - } -} From d985f9ec443c90d000c9b5ee38431f625113c4fa Mon Sep 17 00:00:00 2001 From: Eilon Lipton Date: Thu, 27 Sep 2018 15:46:08 -0700 Subject: [PATCH 259/316] Update LICENSE.txt --- LICENSE.txt | 207 +++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 197 insertions(+), 10 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 7b2956ecee..b3b180cd51 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,14 +1,201 @@ -Copyright (c) .NET Foundation and Contributors + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ -All rights reserved. + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at + 1. Definitions. - http://www.apache.org/licenses/LICENSE-2.0 + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. -Unless required by applicable law or agreed to in writing, software distributed -under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR -CONDITIONS OF ANY KIND, either express or implied. See the License for the -specific language governing permissions and limitations under the License. + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright (c) .NET Foundation and Contributors + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. From ccb6579cd721db118c0d47bf76d777a57bc162bd Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 27 Sep 2018 16:49:56 -0700 Subject: [PATCH 260/316] Do not return ProblemDetails for < 4xx status codes Fixes #8504 --- .../Infrastructure/ClientErrorResultFilter.cs | 7 ++++ .../ClientErrorResultFilterTest.cs | 36 +++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs index fd5911ea07..1ef66b1a33 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs @@ -43,6 +43,13 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure return; } + // We do not have an upper bound on the allowed status code. This allows this filter to be used + // for 5xx and later status codes. + if (clientError.StatusCode < 400) + { + return; + } + var result = _clientErrorFactory.GetClientError(context, clientError); if (result == null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs index 83098744ff..0a2c6db76d 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs @@ -68,6 +68,42 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure Assert.Same(Result, context.Result); } + [Theory] + [InlineData(400)] + [InlineData(409)] + [InlineData(503)] + public void OnResultExecuting_Transforms4XXStatusCodeResult(int statusCode) + { + // Arrange + var actionResult = new StatusCodeResult(statusCode); + var context = GetContext(actionResult); + var filter = GetFilter(); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(Result, context.Result); + } + + [Theory] + [InlineData(201)] + [InlineData(302)] + [InlineData(399)] + public void OnResultExecuting_DoesNotTransformStatusCodesLessThan400(int statusCode) + { + // Arrange + var actionResult = new StatusCodeResult(statusCode); + var context = GetContext(actionResult); + var filter = GetFilter(); + + // Act + filter.OnResultExecuting(context); + + // Assert + Assert.Same(actionResult, context.Result); + } + private static ClientErrorResultFilter GetFilter() { var factory = Mock.Of( From ef21286792ad94652eacdba8558ee65be1288e5e Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 28 Sep 2018 10:36:56 -0700 Subject: [PATCH 261/316] Fix microbuild code signing configuration (#8511) Fixup the paths to files to be signed. --- Directory.Build.props | 3 --- .../Microsoft.AspNetCore.Mvc.Analyzers.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Razor.csproj | 2 +- 4 files changed, 3 insertions(+), 6 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 065616f3cd..6c951ddaca 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,9 +14,6 @@ $(MSBuildThisFileDirectory) $(MSBuildThisFileDirectory)build\Key.snk true - Microsoft - MicrosoftNuGet - true true true diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj index bfaa4f6135..3030951867 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj index a4c48adc4b..50b645a854 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj @@ -20,7 +20,7 @@ - + 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 decdf16adf..b63e6156e9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj @@ -22,7 +22,7 @@ - + From cc65115be1020f00920d78d316db8d390c04db15 Mon Sep 17 00:00:00 2001 From: Nate McMaster Date: Fri, 28 Sep 2018 17:10:37 -0700 Subject: [PATCH 262/316] automated: bulk infrastructure updates. Update bootstrapper scripts and remove unnecessary signing properties --- run.ps1 | 6 +++--- run.sh | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/run.ps1 b/run.ps1 index 3b27382468..34604c7175 100644 --- a/run.ps1 +++ b/run.ps1 @@ -52,8 +52,8 @@ in the file are overridden by command line parameters. Example config file: ```json { - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/dev/tools/korebuild.schema.json", - "channel": "dev", + "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", + "channel": "master", "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" } ``` @@ -192,7 +192,7 @@ if (!$DotNetHome) { else { Join-Path $PSScriptRoot '.dotnet'} } -if (!$Channel) { $Channel = 'dev' } +if (!$Channel) { $Channel = 'master' } if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } # Execute diff --git a/run.sh b/run.sh index 02aac15874..4c1fed5646 100755 --- a/run.sh +++ b/run.sh @@ -220,7 +220,7 @@ if [ -f "$config_file" ]; then config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python ; then @@ -228,7 +228,7 @@ if [ -f "$config_file" ]; then config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi elif __machine_has python3 ; then @@ -236,11 +236,11 @@ if [ -f "$config_file" ]; then config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" else - _error "$config_file contains invalid JSON." + __error "$config_file contains invalid JSON." exit 1 fi else - _error 'Missing required command: jq or python. Could not parse the JSON file.' + __error 'Missing required command: jq or python. Could not parse the JSON file.' exit 1 fi @@ -248,7 +248,7 @@ if [ -f "$config_file" ]; then [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" fi -[ -z "$channel" ] && channel='dev' +[ -z "$channel" ] && channel='master' [ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' get_korebuild From cb1393cbb1ae2ce39fe1d72a1dee38ee849c296f Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 29 Sep 2018 18:10:21 -0700 Subject: [PATCH 263/316] Adding functional tests for LinkGenerator --- build/dependencies.props | 146 ++++++------ .../Properties/Resources.Designer.cs | 8 +- .../Resources.resx | 2 +- .../Routing/UrlHelperBase.cs | 8 +- .../Properties/Resources.Designer.cs | 28 +-- .../Routing/UrlHelperExtensionsTest.cs | 7 +- .../LinkGeneratorTest.cs | 221 ++++++++++++++++++ .../Areas/Admin/LG3Controller.cs | 44 ++++ .../Areas/Admin/Pages/LGAreaPage.cshtml | 4 + .../Areas/Admin/Pages/LGAreaPage.cshtml.cs | 14 ++ .../Controllers/LG1Controller.cs | 119 ++++++++++ .../Controllers/LG2Controller.cs | 14 ++ .../RoutingWebSite/Pages/LGAnotherPage.cshtml | 4 + .../Pages/LGAnotherPage.cshtml.cs | 24 ++ .../RoutingWebSite/Pages/LGPage.cshtml | 4 + .../RoutingWebSite/Pages/LGPage.cshtml.cs | 24 ++ .../RoutingWebSite/RoutingWebSite.csproj | 1 + .../RoutingWebSite/StartupForLinkGenerator.cs | 34 +++ 18 files changed, 611 insertions(+), 95 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs create mode 100644 test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs create mode 100644 test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml create mode 100644 test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs create mode 100644 test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs create mode 100644 test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml create mode 100644 test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs create mode 100644 test/WebSites/RoutingWebSite/Pages/LGPage.cshtml create mode 100644 test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs create mode 100644 test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs diff --git a/build/dependencies.props b/build/dependencies.props index 7d118388d6..0223333d6a 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview3-35301 + 2.2.0-preview3-35358 2.2.0-preview1-20180918.1 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 5.2.6 2.8.0 2.8.0 - 2.2.0-preview3-35301 + 2.2.0-preview3-35358 1.7.0 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 2.1.0 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 2.0.9 2.1.3 - 2.2.0-preview2-26905-02 - 2.2.0-preview3-35301 - 2.2.0-preview3-35301 + 2.2.0-preview3-26927-02 + 2.2.0-preview3-35358 + 2.2.0-preview3-35358 15.6.1 4.7.49 2.0.3 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs index c00c53bcf8..8acff61ab2 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs @@ -1313,7 +1313,7 @@ namespace Microsoft.AspNetCore.Mvc.Core => string.Format(CultureInfo.CurrentCulture, GetString("NoRoutesMatchedForPage"), p0); /// - /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. + /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages. /// internal static string UrlHelper_RelativePagePathIsNotSupported { @@ -1321,10 +1321,10 @@ namespace Microsoft.AspNetCore.Mvc.Core } /// - /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. + /// The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages. /// - internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0); + internal static string FormatUrlHelper_RelativePagePathIsNotSupported(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("UrlHelper_RelativePagePathIsNotSupported"), p0, p1, p2); /// /// One or more validation errors occurred. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx index 30faf9ec02..f1d2e78ade 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx @@ -410,7 +410,7 @@ No page named '{0}' matches the supplied values. - The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. + The relative page path '{0}' can only be used while executing a Razor Page. Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. If you are using {1} then you must provide the current {2} to use relative pages. One or more validation errors occurred. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs index 5c48596532..c468e5280c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs @@ -361,7 +361,13 @@ namespace Microsoft.AspNetCore.Mvc.Routing if (string.IsNullOrEmpty(currentPagePath)) { // Disallow the use sibling page routing, a Razor page specific feature, from a non-page action. - throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported(pageName)); + // OR - this is a call from LinkGenerator where the HttpContext was not specified. + // + // We can't use a relative path in either case, because we don't know the base path. + throw new InvalidOperationException(Resources.FormatUrlHelper_RelativePagePathIsNotSupported( + pageName, + nameof(LinkGenerator), + nameof(HttpContext))); } return ViewEnginePath.CombinePath(currentPagePath, pageName); diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs index 16f29e1d67..aa246881eb 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs @@ -10,6 +10,20 @@ namespace Microsoft.AspNetCore.Mvc.Testing private static readonly ResourceManager _resourceManager = new ResourceManager("Microsoft.AspNetCore.Mvc.Testing.Resources", typeof(Resources).GetTypeInfo().Assembly); + /// + /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. + /// + internal static string InvalidAssemblyEntryPoint + { + get => GetString("InvalidAssemblyEntryPoint"); + } + + /// + /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. + /// + internal static string FormatInvalidAssemblyEntryPoint(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0); + /// /// No method 'public static {0} CreateWebHostBuilder(string[] args)' found on '{1}'. Alternatively, {2} can be extended and 'protected virtual {0} {3}()' can be overridden to provide your own {0} instance. /// @@ -38,20 +52,6 @@ namespace Microsoft.AspNetCore.Mvc.Testing internal static string FormatMissingDepsFile(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("MissingDepsFile"), p0, p1); - /// - /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. - /// - internal static string InvalidAssemblyEntryPoint - { - get => GetString("InvalidAssemblyEntryPoint"); - } - - /// - /// The provided Type '{0}' does not belong to an assembly with an entry point. A common cause for this error is providing a Type from a class library. - /// - internal static string FormatInvalidAssemblyEntryPoint(string p0) - => string.Format(CultureInfo.CurrentCulture, GetString("InvalidAssemblyEntryPoint"), p0); - private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs index f6b212c7e5..24d9ce80f5 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs @@ -501,8 +501,11 @@ namespace Microsoft.AspNetCore.Mvc.Core.Test.Routing // Act & Assert var ex = Assert.Throws(() => urlHelper.Object.Page(expected)); - Assert.Equal($"The relative page path '{expected}' can only be used while executing a Razor Page. " + - "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page.", ex.Message); + Assert.Equal( + $"The relative page path '{expected}' can only be used while executing a Razor Page. " + + "Specify a root relative path with a leading '/' to generate a URL outside of a Razor Page. " + + "If you are using LinkGenerator then you must provide the current HttpContext to use relative pages.", + ex.Message); } [Fact] diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs new file mode 100644 index 0000000000..755a844425 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs @@ -0,0 +1,221 @@ +// 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.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + // Functional tests for MVC's scenarios with LinkGenerator (2.2+ only) + public class LinkGeneratorTest : IClassFixture> + { + public LinkGeneratorTest(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; } + + [Fact] + public async Task GetPathByAction_CanGeneratePathToSelf() + { + // Act + var response = await Client.GetAsync("LG1/LinkToSelf"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LG1/LinkToSelf", responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathToSelf_PreserveAmbientValues() + { + // Act + var response = await Client.GetAsync("LG1/LinkToSelf/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LG1/LinkToSelf/17?another-value=5", responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathToAnotherAction_RemovesAmbientValues() + { + // Act + var response = await Client.GetAsync("LG1/LinkToAnotherAction/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LG1/LinkToSelf?another-value=5", responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathToAnotherController_RemovesAmbientValues() + { + // Act + var response = await Client.GetAsync("LG1/LinkToAnotherController/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LG2/SomeAction?another-value=5", responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathToAnotherControllerInArea_RemovesAmbientValues() + { + // Act + var response = await Client.GetAsync("LG1/LinkToAnArea/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/Admin/LG3/SomeAction?another-value=5", responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathWithinArea() + { + // Act + var response = await Client.GetAsync("Admin/LG3/LinkInsideOfArea/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/Admin/LG3/SomeAction", responseContent); + } + + // Rejected because the calling code relies on ambient values, but doesn't pass + // the HttpContext. + [Fact] + public async Task GetPathByAction_FailsToGenerateLinkInsideArea() + { + // Act + var response = await Client.GetAsync("Admin/LG3/LinkInsideOfAreaFail/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.Equal(string.Empty, responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathOutsideOfArea() + { + // Act + var response = await Client.GetAsync("Admin/LG3/LinkOutsideOfArea/17?another-value=5"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.NoContent, response.StatusCode); + Assert.Equal(string.Empty, responseContent); + } + + [Fact] + public async Task GetPathByAction_CanGeneratePathFromPath() + { + // Act + var response = await Client.GetAsync("LGAnotherPage/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LG2/SomeAction", responseContent); + } + + [Fact] + public async Task GetPathByPage_FromPage_CanGeneratePathWithRelativePageName() + { + // Act + var response = await Client.GetAsync("LGPage/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LGAnotherPage", responseContent); + } + + [Fact] + public async Task GetPathByPage_CanGeneratePathToPage() + { + // Act + var response = await Client.GetAsync("LG1/LinkToPage/17?another-value=4"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/LGPage?another-value=4", responseContent); + } + + [Fact] + public async Task GetPathByPage_CanGeneratePathToPageInArea() + { + // Act + var response = await Client.GetAsync("LG1/LinkToPageInArea/17?another-value=4"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/Admin/LGAreaPage?another-value=4&handler=a-handler", responseContent); + } + + [Fact] + public async Task GetUriByAction_CanGenerateFullUri() + { + // Act + var response = await Client.GetAsync("LG1/LinkWithFullUri/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("http://localhost/LG1/LinkWithFullUri/17#hi", responseContent); + } + + [Fact] + public async Task GetUriByAction_CanGenerateFullUri_WithoutHttpContext() + { + // Act + var response = await Client.GetAsync("LG1/LinkWithFullUriWithoutHttpContext/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("https://www.example.com/LG1/LinkWithFullUri#hi", responseContent); + } + + [Fact] + public async Task GetUriByPage_CanGenerateFullUri() + { + // Act + var response = await Client.GetAsync("LG1/LinkToPageWithFullUri/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("http://localhost/LGPage", responseContent); + } + + [Fact] + public async Task GetUriByPage_CanGenerateFullUri_WithoutHttpContext() + { + // Act + var response = await Client.GetAsync("LG1/LinkToPageWithFullUriWithoutHttpContext/17"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("https://www.example.com/Admin/LGAreaPage?handler=a-handler", responseContent); + } + } +} diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs b/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs new file mode 100644 index 0000000000..dafe56ee22 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs @@ -0,0 +1,44 @@ +// 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; +using Microsoft.AspNetCore.Routing; + +namespace RoutingWebSite +{ + [Area("Admin")] + [Route("[area]/[controller]/[action]/{id?}")] + public class LG3Controller : Controller + { + private readonly LinkGenerator _linkGenerator; + + public LG3Controller(LinkGenerator linkGenerator) + { + _linkGenerator = linkGenerator; + } + + public void SomeAction() + { + } + + public string LinkInsideOfArea() + { + return _linkGenerator.GetPathByAction(HttpContext, action: nameof(SomeAction)); + } + + public string LinkInsideOfAreaFail() + { + // No ambient values - this will fail. + return _linkGenerator.GetPathByAction(controller: "LG3", action: nameof(SomeAction)); + } + + public string LinkOutsideOfArea() + { + return _linkGenerator.GetPathByAction( + HttpContext, + action: nameof(SomeAction), + controller: "LG1", + values: new { area = "", }); + } + } +} diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml new file mode 100644 index 0000000000..11f5e9acec --- /dev/null +++ b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml @@ -0,0 +1,4 @@ +@page "{id?}" +@model RoutingWebSite.Areas.Admin.Pages.LGAreaPageModel +@{ +} diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs new file mode 100644 index 0000000000..bc3137d121 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs @@ -0,0 +1,14 @@ +// 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.RazorPages; + +namespace RoutingWebSite.Areas.Admin.Pages +{ + public class LGAreaPageModel : PageModel + { + public void OnGet() + { + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs new file mode 100644 index 0000000000..f8fb863add --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs @@ -0,0 +1,119 @@ +// 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.Linq; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Routing; + +namespace RoutingWebSite +{ + public class LG1Controller : Controller + { + private readonly LinkGenerator _linkGenerator; + + public LG1Controller(LinkGenerator linkGenerator) + { + _linkGenerator = linkGenerator; + } + + public string LinkToSelf() + { + return _linkGenerator.GetPathByAction(HttpContext, values: QueryToRouteValues(HttpContext.Request.Query)); + } + + public string LinkToAnotherAction() + { + return _linkGenerator.GetPathByAction( + HttpContext, + action: nameof(LinkToSelf), + values: QueryToRouteValues(HttpContext.Request.Query)); + } + + public string LinkToAnotherController() + { + return _linkGenerator.GetPathByAction( + HttpContext, + controller: "LG2", + action: nameof(LG2Controller.SomeAction), + values: QueryToRouteValues(HttpContext.Request.Query)); + } + + public string LinkToAnArea() + { + var values = QueryToRouteValues(HttpContext.Request.Query); + values["area"] = "Admin"; + + return _linkGenerator.GetPathByAction( + HttpContext, + controller: "LG3", + action: nameof(LG3Controller.SomeAction), + values: values); + } + + public string LinkToPage() + { + return _linkGenerator.GetPathByPage( + HttpContext, + page: "/LGPage", + values: QueryToRouteValues(HttpContext.Request.Query)); + } + + public string LinkToPageInArea() + { + var values = QueryToRouteValues(HttpContext.Request.Query); + values["area"] = "Admin"; + return _linkGenerator.GetPathByPage( + HttpContext, + page: "/LGAreaPage", + handler: "a-handler", + values: values); + } + + public string LinkWithFullUri() + { + return _linkGenerator.GetUriByAction( + HttpContext, + controller: "LG1", + action: nameof(LinkWithFullUri), + values: QueryToRouteValues(HttpContext.Request.Query), + fragment: new FragmentString("#hi")); + } + + public string LinkToPageWithFullUri() + { + return _linkGenerator.GetUriByPage( + HttpContext, + page: "/LGPage", + values: QueryToRouteValues(HttpContext.Request.Query)); + } + + public string LinkWithFullUriWithoutHttpContext() + { + return _linkGenerator.GetUriByAction( + scheme: "https", + host: new HostString("www.example.com"), + controller: "LG1", + action: nameof(LinkWithFullUri), + values: QueryToRouteValues(HttpContext.Request.Query), + fragment: new FragmentString("#hi")); + } + + public string LinkToPageWithFullUriWithoutHttpContext() + { + var values = QueryToRouteValues(HttpContext.Request.Query); + values["area"] = "Admin"; + return _linkGenerator.GetUriByPage( + scheme: "https", + host: new HostString("www.example.com"), + page: "/LGAreaPage", + handler: "a-handler", + values: values); + } + + private static RouteValueDictionary QueryToRouteValues(IQueryCollection query) + { + return new RouteValueDictionary(query.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ToString())); + } + } +} diff --git a/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs b/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs new file mode 100644 index 0000000000..b8a25750fe --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs @@ -0,0 +1,14 @@ +// 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; + +namespace RoutingWebSite +{ + public class LG2Controller : Controller + { + public void SomeAction() + { + } + } +} diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml new file mode 100644 index 0000000000..d9a0e02177 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml @@ -0,0 +1,4 @@ +@page "{id?}" +@model RoutingWebSite.Pages.LGAnotherPageModel +@{ +} diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs new file mode 100644 index 0000000000..1cc95da552 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs @@ -0,0 +1,24 @@ +// 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; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Routing; + +namespace RoutingWebSite.Pages +{ + public class LGAnotherPageModel : PageModel + { + private readonly LinkGenerator _linkGenerator; + + public LGAnotherPageModel(LinkGenerator linkGenerator) + { + _linkGenerator = linkGenerator; + } + + public ContentResult OnGet() + { + return Content(_linkGenerator.GetPathByAction(HttpContext, action: nameof(LG2Controller.SomeAction), controller: "LG2")); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml new file mode 100644 index 0000000000..598b475d1c --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml @@ -0,0 +1,4 @@ +@page "{id?}" +@model BasicWebSite.Pages.LGPageModel +@{ +} diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs new file mode 100644 index 0000000000..56e82afd2d --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs @@ -0,0 +1,24 @@ +// 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; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Microsoft.AspNetCore.Routing; + +namespace BasicWebSite.Pages +{ + public class LGPageModel : PageModel + { + private readonly LinkGenerator _linkGenerator; + + public LGPageModel(LinkGenerator linkGenerator) + { + _linkGenerator = linkGenerator; + } + + public ContentResult OnGet() + { + return Content(_linkGenerator.GetPathByPage(HttpContext, "./LGAnotherPage")); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj index cfda2f9c35..212a619b2e 100644 --- a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj +++ b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj @@ -10,5 +10,6 @@ + diff --git a/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs new file mode 100644 index 0000000000..3a85de8fda --- /dev/null +++ b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs @@ -0,0 +1,34 @@ +// 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.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.DependencyInjection; + +namespace RoutingWebSite +{ + // A very basic routing configuration for LinkGenerator tests + public class StartupForLinkGenerator + { + public void ConfigureServices(IServiceCollection services) + { + services + .AddMvc() + .SetCompatibilityVersion(CompatibilityVersion.Latest); + services + .AddRouting(options => + { + options.ConstraintMap["slugify"] = typeof(SlugifyParameterTransformer); + }); + + services.AddScoped(); + services.AddSingleton(); + } + + public void Configure(IApplicationBuilder app) + { + app.UseMvcWithDefaultRoute(); + } + } +} \ No newline at end of file From 3e43cb003392dcb35fc26fe5c1d6996788d8b21b Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Sun, 30 Sep 2018 12:25:19 -0700 Subject: [PATCH 264/316] Update dependencies.props [auto-updated: dependencies] --- build/dependencies.props | 146 +++++++++++++++++++-------------------- korebuild-lock.txt | 4 +- 2 files changed, 75 insertions(+), 75 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 0223333d6a..e8d06421c4 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -16,87 +16,87 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview3-35358 - 2.2.0-preview1-20180918.1 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 + 2.2.0-preview3-35359 + 2.2.0-preview1-20180928.5 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 5.2.6 2.8.0 2.8.0 - 2.2.0-preview3-35358 + 2.2.0-preview3-35359 1.7.0 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 2.1.0 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 2.0.9 2.1.3 2.2.0-preview3-26927-02 - 2.2.0-preview3-35358 - 2.2.0-preview3-35358 + 2.2.0-preview3-35359 + 2.2.0-preview3-35359 15.6.1 4.7.49 2.0.3 diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 649bf2ba0b..26697a21fa 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180918.1 -commithash:ad5e3fc53442741a0dd49bce437d2ac72f4b5800 +version:2.2.0-preview1-20180928.5 +commithash:43faa29f679f47b88689d645b39e6be5e0055d70 From 3667b0481add083b270f882488997ac4466d2b53 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sat, 29 Sep 2018 21:09:27 -0700 Subject: [PATCH 265/316] React to routing API changes Reaction for: https://github.com/aspnet/Routing/pull/822 --- build/dependencies.props | 4 +- .../Routing/ActionConstraintMatcherPolicy.cs | 10 +-- .../Internal/MvcEndpointDataSourceTests.cs | 2 +- .../ControllerLinkGeneratorExtensionsTest.cs | 2 +- .../Routing/EndpointRoutingUrlHelperTest.cs | 4 +- .../PageLinkGeneratorExtensionsTest.cs | 2 +- .../ActionConstraintMatcherPolicyTest.cs | 78 +++++++++---------- 7 files changed, 50 insertions(+), 52 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index e8d06421c4..e47c6782ea 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -48,8 +48,8 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-a-preview3-endpoint-selector-17041 + 2.2.0-a-preview3-endpoint-selector-17041 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index e03640b59c..488b15646b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -38,7 +38,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Internal for testing internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints; - public Task ApplyAsync(HttpContext httpContext, EndpointFeature endpointFeature, CandidateSet candidateSet) + public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) { // PERF: we can skip over action constraints if there aren't any app-wide. // @@ -64,14 +64,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing // set as valid. This is O(2n) vs O(n**2) for (var i = 0; i < candidateSet.Count; i++) { - candidateSet[i].IsValidCandidate = false; + candidateSet.SetValidity(i, false); } if (finalMatches != null) { for (var i = 0; i < finalMatches.Count; i++) { - candidateSet[finalMatches[i].index].IsValidCandidate = true; + candidateSet.SetValidity(finalMatches[i].index, true); } } } @@ -91,9 +91,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Perf: Avoid allocations for (var i = 0; i < candidateSet.Count; i++) { - ref var candidate = ref candidateSet[i]; - if (candidate.IsValidCandidate) + if (candidateSet.IsValidCandidate(i)) { + ref var candidate = ref candidateSet[i]; if (score != null && score != candidate.Score) { // This is the end of a group. diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 168ae2950f..d35dea52ca 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -81,7 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal public async Task Endpoints_InvokeReturnedEndpoint_ActionInvokerProviderCalled() { // Arrange - var endpointFeature = new EndpointFeature + var endpointFeature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary() }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs index 5d0f01fafb..29a197d67f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs @@ -232,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing { var httpContext = new DefaultHttpContext(); - var feature = new EndpointFeature + var feature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) }; diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index 6853dd7180..bf423d775e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -60,7 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); // Set the endpoint feature and current context just as a normal request to MVC app would be - var endpointFeature = new EndpointFeature(); + var endpointFeature = new EndpointSelectorContext(); urlHelper.ActionContext.HttpContext.Features.Set(endpointFeature); urlHelper.ActionContext.HttpContext.Features.Set(endpointFeature); endpointFeature.Endpoint = endpoint1; @@ -154,7 +154,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing protected override IUrlHelper CreateUrlHelper(ActionContext actionContext) { var httpContext = actionContext.HttpContext; - httpContext.Features.Set(new EndpointFeature() + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint( context => Task.CompletedTask, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs index f07f150472..89531042f1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs @@ -232,7 +232,7 @@ namespace Microsoft.AspNetCore.Routing { var httpContext = new DefaultHttpContext(); - var feature = new EndpointFeature + var feature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) }; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 9889b12e1b..5210180832 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -35,11 +35,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var selector = CreateSelector(actions); // Act - await selector.ApplyAsync(new DefaultHttpContext(), new EndpointFeature(), candidateSet); + await selector.ApplyAsync(new DefaultHttpContext(), new EndpointSelectorContext(), candidateSet); // Assert - Assert.True(candidateSet[0].IsValidCandidate); - Assert.True(candidateSet[1].IsValidCandidate); + Assert.True(candidateSet.IsValidCandidate(0)); + Assert.True(candidateSet.IsValidCandidate(1)); } [Fact] @@ -68,11 +68,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.True(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); + Assert.True(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); } [Fact] @@ -103,11 +103,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.False(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); + Assert.False(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); } [Fact] @@ -139,11 +139,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.False(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); + Assert.False(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); } // Due to ordering of stages, the first action will be better. @@ -174,11 +174,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.True(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); + Assert.True(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); } [Fact] @@ -205,19 +205,19 @@ namespace Microsoft.AspNetCore.Mvc.Routing var actions = new ActionDescriptor[] { best, another, worst }; var candidateSet = CreateCandidateSet(actions); - candidateSet[0].IsValidCandidate = false; - candidateSet[1].IsValidCandidate = false; + candidateSet.SetValidity(0, false); + candidateSet.SetValidity(1, false); var selector = CreateSelector(actions); var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.False(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); - Assert.True(candidateSet[2].IsValidCandidate); + Assert.False(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); + Assert.True(candidateSet.IsValidCandidate(2)); } [Fact] @@ -247,12 +247,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.False(candidateSet[0].IsValidCandidate); - Assert.True(candidateSet[1].IsValidCandidate); - Assert.False(candidateSet[2].IsValidCandidate); + Assert.False(candidateSet.IsValidCandidate(0)); + Assert.True(candidateSet.IsValidCandidate(1)); + Assert.False(candidateSet.IsValidCandidate(2)); } // Due to ordering of stages, the first action will be better. @@ -288,11 +288,11 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.True(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); + Assert.True(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); } [Fact] @@ -329,12 +329,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing var httpContext = CreateHttpContext("POST"); // Act - await selector.ApplyAsync(httpContext, new EndpointFeature(), candidateSet); + await selector.ApplyAsync(httpContext, new EndpointSelectorContext(), candidateSet); // Assert - Assert.True(candidateSet[0].IsValidCandidate); - Assert.False(candidateSet[1].IsValidCandidate); - Assert.False(candidateSet[2].IsValidCandidate); + Assert.True(candidateSet.IsValidCandidate(0)); + Assert.False(candidateSet.IsValidCandidate(1)); + Assert.False(candidateSet.IsValidCandidate(2)); } [Fact] @@ -452,18 +452,16 @@ namespace Microsoft.AspNetCore.Mvc.Routing private static CandidateSet CreateCandidateSet(ActionDescriptor[] actions) { - var candidateSet = new CandidateSet( - actions.Select(CreateEndpoint).ToArray(), - new int[actions.Length]); - + var values = new RouteValueDictionary[actions.Length]; for (var i = 0; i < actions.Length; i++) { - if (candidateSet[i].IsValidCandidate) - { - candidateSet[i].Values = new RouteValueDictionary(); - } + values[i] = new RouteValueDictionary(); } + var candidateSet = new CandidateSet( + actions.Select(CreateEndpoint).ToArray(), + values, + new int[actions.Length]); return candidateSet; } From a48e75dfb475a2fab28e555df531c83dda31cc2c Mon Sep 17 00:00:00 2001 From: Pranav K Date: Tue, 2 Oct 2018 10:24:38 -0700 Subject: [PATCH 266/316] Implicitly set content type for ObjectResults containing ProblemDetails (#8512) * Implicitly set content type for ObjectResults containing ProblemDetails Fixes #8467 --- .../Infrastructure/ObjectResultExecutor.cs | 43 +++++---- .../ObjectResult.cs | 10 +- .../ObjectResultExecutorTest.cs | 96 +++++++++++++++++++ 3 files changed, 130 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs index 5d5fd81b23..d48d213532 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Text; using System.Threading.Tasks; @@ -84,21 +85,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure throw new ArgumentNullException(nameof(result)); } - // If the user sets the content type both on the ObjectResult (example: by Produces) and Response object, - // then the one set on ObjectResult takes precedence over the Response object - if (result.ContentTypes == null || result.ContentTypes.Count == 0) - { - var responseContentType = context.HttpContext.Response.ContentType; - if (!string.IsNullOrEmpty(responseContentType)) - { - if (result.ContentTypes == null) - { - result.ContentTypes = new MediaTypeCollection(); - } - - result.ContentTypes.Add(responseContentType); - } - } + InferContentTypes(context, result); var objectType = result.DeclaredType; if (objectType == null || objectType == typeof(object)) @@ -113,8 +100,8 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure result.Value); var selectedFormatter = FormatterSelector.SelectFormatter( - formatterContext, - (IList)result.Formatters ?? Array.Empty(), + formatterContext, + (IList)result.Formatters ?? Array.Empty(), result.ContentTypes); if (selectedFormatter == null) { @@ -130,5 +117,27 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure result.OnFormatting(context); return selectedFormatter.WriteAsync(formatterContext); } + + private static void InferContentTypes(ActionContext context, ObjectResult result) + { + Debug.Assert(result.ContentTypes != null); + if (result.ContentTypes.Count != 0) + { + return; + } + + // If the user sets the content type both on the ObjectResult (example: by Produces) and Response object, + // then the one set on ObjectResult takes precedence over the Response object + var responseContentType = context.HttpContext.Response.ContentType; + if (!string.IsNullOrEmpty(responseContentType)) + { + result.ContentTypes.Add(responseContentType); + } + else if (result.Value is ProblemDetails) + { + result.ContentTypes.Add("application/problem+json"); + result.ContentTypes.Add("application/problem+xml"); + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs index bb7fc0522d..a5a0323089 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs @@ -11,11 +11,13 @@ namespace Microsoft.AspNetCore.Mvc { public class ObjectResult : ActionResult, IStatusCodeActionResult { + private MediaTypeCollection _contentTypes; + public ObjectResult(object value) { Value = value; Formatters = new FormatterCollection(); - ContentTypes = new MediaTypeCollection(); + _contentTypes = new MediaTypeCollection(); } [ActionResultObjectValue] @@ -23,7 +25,11 @@ namespace Microsoft.AspNetCore.Mvc public FormatterCollection Formatters { get; set; } - public MediaTypeCollection ContentTypes { get; set; } + public MediaTypeCollection ContentTypes + { + get => _contentTypes; + set => _contentTypes = value ?? throw new ArgumentNullException(nameof(value)); + } public Type DeclaredType { get; set; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs index 04013644df..207d04161f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs @@ -17,6 +17,32 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { public class ObjectResultExecutorTest { + [Fact] + public async Task ExecuteAsync_UsesSpecifiedContentType() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Request.Headers[HeaderNames.Accept] = "application/xml"; // This will not be used + httpContext.Response.ContentType = "text/json"; + + var result = new ObjectResult("input") + { + ContentTypes = { "text/xml", }, + }; + result.Formatters.Add(new TestXmlOutputFormatter()); + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestStringOutputFormatter()); // This will be chosen based on the content type + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("text/xml; charset=utf-8", httpContext.Response.ContentType); + } + // For this test case probably the most common use case is when there is a format mapping based // content type selected but the developer had set the content type on the Response.ContentType [Fact] @@ -85,6 +111,74 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure Assert.Equal(406, httpContext.Response.StatusCode); } + [Fact] + public async Task ExecuteAsync_ForProblemDetailsValue_UsesSpecifiedContentType() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Response.ContentType = "application/json"; + + var result = new ObjectResult(new ProblemDetails()) + { + ContentTypes = { "text/plain" }, + }; + result.Formatters.Add(new TestXmlOutputFormatter()); + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestStringOutputFormatter()); // This will be chosen based on the content type + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("text/plain; charset=utf-8", httpContext.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_ForProblemDetailsValue_UsesResponseContentType() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + httpContext.Response.ContentType = "application/json"; + + var result = new ObjectResult(new ProblemDetails()); + result.Formatters.Add(new TestXmlOutputFormatter()); + result.Formatters.Add(new TestJsonOutputFormatter()); // This will be chosen based on the response content type + result.Formatters.Add(new TestStringOutputFormatter()); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("application/json; charset=utf-8", httpContext.Response.ContentType); + } + + [Fact] + public async Task ExecuteAsync_NoContentTypeProvidedForProblemDetails_UsesDefaultContentTypes() + { + // Arrange + var executor = CreateExecutor(); + + var httpContext = new DefaultHttpContext(); + var actionContext = new ActionContext() { HttpContext = httpContext }; + + var result = new ObjectResult(new ProblemDetails()); + result.Formatters.Add(new TestXmlOutputFormatter()); // This will be chosen based on the implicitly added content type + result.Formatters.Add(new TestJsonOutputFormatter()); + result.Formatters.Add(new TestStringOutputFormatter()); + + // Act + await executor.ExecuteAsync(actionContext, result); + + // Assert + MediaTypeAssert.Equal("application/problem+xml; charset=utf-8", httpContext.Response.ContentType); + } + [Fact] public async Task ExecuteAsync_NoFormatterFound_Returns406() { @@ -310,6 +404,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json")); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/json")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/*+json")); SupportedEncodings.Add(Encoding.UTF8); } @@ -326,6 +421,7 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure { SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml")); SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml")); + SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/*+xml")); SupportedEncodings.Add(Encoding.UTF8); } From 70ddf15cbc988a39048e696595a2e86e7c6c2d80 Mon Sep 17 00:00:00 2001 From: Alexej Timonin Date: Tue, 2 Oct 2018 20:49:46 +0200 Subject: [PATCH 267/316] MethodMatches :shower: --- .../ApiExplorer/ApiConventionMatcher.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs index b9e6f593c3..e080ce4ef4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs @@ -15,12 +15,7 @@ namespace Microsoft.AspNetCore.Mvc.ApiExplorer bool MethodMatches() { var methodNameMatchBehavior = GetNameMatchBehavior(conventionMethod); - if (!IsNameMatch(methodInfo.Name, conventionMethod.Name, methodNameMatchBehavior)) - { - return false; - } - - return true; + return IsNameMatch(methodInfo.Name, conventionMethod.Name, methodNameMatchBehavior); } bool ParametersMatch() From 7854d65c117ae3e67ee7be14a747f6ba66148d4e Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 4 Oct 2018 14:39:40 +1300 Subject: [PATCH 268/316] Support page parameter in attribute route (#8530) --- .../Internal/MvcEndpointDataSource.cs | 29 ++++--- .../Internal/MvcEndpointDataSourceTests.cs | 36 +++++++++ .../LinkBuilder.cs | 47 ++++++++++++ .../RoutingResult.cs | 25 +++++++ .../BasicTests.cs | 4 +- ...soft.AspNetCore.Mvc.FunctionalTests.csproj | 3 +- ...gTest.cs => RoutingEndpointRoutingTest.cs} | 26 ++++++- ...ngEndpointRoutingWithoutRazorPagesTests.cs | 19 +++++ .../RoutingTestsBase.cs | 75 +++++-------------- .../RoutingWithoutRazorPagesTests.cs | 19 +++++ .../RoutingWithoutRazorPagesTestsBase.cs | 68 +++++++++++++++++ .../WebSites/BasicWebSite/BasicWebSite.csproj | 4 + .../Controllers/PageRouteController.cs | 22 +++++- test/WebSites/BasicWebSite/Startup.cs | 3 + .../StartupWithEndpointRouting.cs | 5 ++ ...Route.cshtml => AttributeRouteView.cshtml} | 0 ...te.cshtml => ConventionalRouteView.cshtml} | 0 .../TestResponseGenerator.cs | 6 +- .../Controllers/PageRouteController.cs | 28 +++++++ ...emoveControllerActionDescriptorProvider.cs | 20 +++-- .../RoutingWebSite/RoutingWebSite.csproj | 4 + test/WebSites/RoutingWebSite/Startup.cs | 6 ++ .../RoutingWebSite/StartupWith21Compat.cs | 12 ++- .../RoutingWebSite/TestResponseGenerator.cs | 61 --------------- .../VersioningWebSite.csproj | 4 + 25 files changed, 383 insertions(+), 143 deletions(-) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs rename test/Microsoft.AspNetCore.Mvc.FunctionalTests/{EndpointRoutingTest.cs => RoutingEndpointRoutingTest.cs} (93%) create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs rename test/WebSites/BasicWebSite/Views/PageRoute/{AttributeRoute.cshtml => AttributeRouteView.cshtml} (100%) rename test/WebSites/BasicWebSite/Views/PageRoute/{ConventionalRoute.cshtml => ConventionalRouteView.cshtml} (100%) rename test/WebSites/{VersioningWebSite => Common}/TestResponseGenerator.cs (96%) create mode 100644 test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs delete mode 100644 test/WebSites/RoutingWebSite/TestResponseGenerator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index 0a1aa08e24..d371d02f79 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -268,24 +268,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } + // Replace parameter with literal value var parameterRouteValue = action.RouteValues[parameterPart.Name]; - // Replace parameter with literal value - if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) + // Route value could be null if it is a "known" route value. + // Do not use the null value to de-normalize the route pattern, + // instead leave the parameter unchanged. + // e.g. + // RouteValues will contain a null "page" value if there are Razor pages + // Skip replacing the {page} parameter + if (parameterRouteValue != null) { - // Check if the parameter has a transformer policy - // Use the first transformer policy - for (var k = 0; k < parameterPolicies.Count; k++) + if (allParameterPolicies.TryGetValue(parameterPart.Name, out var parameterPolicies)) { - if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) + // Check if the parameter has a transformer policy + // Use the first transformer policy + for (var k = 0; k < parameterPolicies.Count; k++) { - parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); - break; + if (parameterPolicies[k] is IOutboundParameterTransformer parameterTransformer) + { + parameterRouteValue = parameterTransformer.TransformOutbound(parameterRouteValue); + break; + } } } - } - segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); + segmentParts[j] = RoutePatternFactory.LiteralPart(parameterRouteValue); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index d35dea52ca..24622f85cd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -251,6 +251,42 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Collection(endpoints, inspectors); } + [Fact] + public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction", page = (string)null }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + string.Empty, + "{controller}/{action}/{page}", + new RouteValueDictionary(new { action = "TestAction" }))); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Empty(endpoints); + } + + [Fact] + public void Endpoints_SingleAction_AttributeRoute_ContainsParameterWithNullRequiredRouteValue() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + "{controller}/{action}/{page}", + new { controller = "TestController", action = "TestAction", page = (string)null }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection(endpoints, + (e) => Assert.Equal("TestController/TestAction/{page}", Assert.IsType(e).RoutePattern.RawText)); + } + [Fact] public void Endpoints_SingleAction_WithActionDefault() { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs new file mode 100644 index 0000000000..94d7a90f4c --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs @@ -0,0 +1,47 @@ +// 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 Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc +{ + public class LinkBuilder + { + public LinkBuilder(string url) + { + Url = url; + + Values = new Dictionary + { + { "link", string.Empty } + }; + } + + public string Url { get; set; } + + public Dictionary Values { get; set; } + + public LinkBuilder To(object values) + { + var dictionary = new RouteValueDictionary(values); + foreach (var kvp in dictionary) + { + Values.Add("link_" + kvp.Key, kvp.Value); + } + + return this; + } + + public override string ToString() + { + return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); + } + + public static implicit operator string(LinkBuilder builder) + { + return builder.ToString(); + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs new file mode 100644 index 0000000000..3a72c75fae --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs @@ -0,0 +1,25 @@ +// 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; + +namespace Microsoft.AspNetCore.Mvc +{ + // See TestResponseGenerator for the code that generates this data. + public 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; } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs index 6761579f04..ffaebb4fe0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs @@ -382,7 +382,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var expected = "ConventionalRoute - Hello from mypage"; // Act - var response = await Client.GetStringAsync("/PageRoute/ConventionalRoute/mypage"); + var response = await Client.GetStringAsync("/PageRoute/ConventionalRouteView/mypage"); // Assert Assert.Equal(expected, response.Trim()); @@ -395,7 +395,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var expected = "AttributeRoute - Hello from test-page"; // Act - var response = await Client.GetStringAsync("/PageRoute/Attribute/test-page"); + var response = await Client.GetStringAsync("/PageRoute/AttributeView/test-page"); // Assert Assert.Equal(expected, response.Trim()); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index bce36312a4..3573b91863 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -10,7 +10,6 @@ - @@ -29,6 +28,8 @@ + + diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs similarity index 93% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs rename to test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs index daee488bae..e8a3d6a97d 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/EndpointRoutingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Threading.Tasks; @@ -10,13 +11,34 @@ using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests { - public class EndpointRoutingTest : RoutingTestsBase + public class RoutingEndpointRoutingTest : RoutingTestsBase { - public EndpointRoutingTest(MvcTestFixture fixture) + public RoutingEndpointRoutingTest(MvcTestFixture fixture) : base(fixture) { } + [Fact] + public async Task AttributeRoutedAction_ContainsPage_RouteMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls); + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("AttributeRoute", result.Action); + + Assert.Contains( + new KeyValuePair("page", "pagevalue"), + result.RouteValues); + } + [Fact] public async Task ParameterTransformer_TokenReplacement_Found() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs new file mode 100644 index 0000000000..8b597e0ab8 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs @@ -0,0 +1,19 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class RoutingEndpointRoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase + { + public RoutingEndpointRoutingWithoutRazorPagesTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index fbdb36f53d..b5b407d4e2 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -27,6 +27,25 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public HttpClient Client { get; } + [Fact] + public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("ConventionalRoute", result.Action); + + // pagevalue is not used in "page" route value because it is a required value + Assert.False(result.RouteValues.ContainsKey("page")); + } + [Fact] public abstract Task HasEndpointMatch(); @@ -1282,61 +1301,5 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { return new LinkBuilder(url); } - - // See TestResponseGenerator in RoutingWebSite for the code that generates this data. - protected 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; } - } - - protected class LinkBuilder - { - public LinkBuilder(string url) - { - Url = url; - - Values = new Dictionary - { - { "link", string.Empty } - }; - } - - public string Url { get; set; } - - public Dictionary Values { get; set; } - - public LinkBuilder To(object values) - { - var dictionary = new RouteValueDictionary(values); - foreach (var kvp in dictionary) - { - Values.Add("link_" + kvp.Key, kvp.Value); - } - - return this; - } - - public override string ToString() - { - return Url + "?" + string.Join("&", Values.Select(kvp => kvp.Key + "=" + kvp.Value)); - } - - public static implicit operator string(LinkBuilder builder) - { - return builder.ToString(); - } - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs new file mode 100644 index 0000000000..6e90048fc6 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs @@ -0,0 +1,19 @@ +// 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.Net; +using System.Net.Http; +using System.Threading.Tasks; +using Newtonsoft.Json; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.FunctionalTests +{ + public class RoutingWithoutRazorPagesTests : RoutingWithoutRazorPagesTestsBase + { + public RoutingWithoutRazorPagesTests(MvcTestFixture fixture) + : base(fixture) + { + } + } +} \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs new file mode 100644 index 0000000000..061bf537fe --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs @@ -0,0 +1,68 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.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 RoutingWithoutRazorPagesTestsBase : IClassFixture> where TStartup : class + { + protected RoutingWithoutRazorPagesTestsBase(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; } + + [Fact] + public async Task AttributeRoutedAction_ContainsPage_RouteMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/Attribute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Contains("/PageRoute/Attribute/pagevalue", result.ExpectedUrls); + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("AttributeRoute", result.Action); + + Assert.Contains( + new KeyValuePair("page", "pagevalue"), + result.RouteValues); + } + + [Fact] + public async Task ConventionalRoutedAction_RouteContainsPage_RouteNotMatched() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRoute/ConventionalRoute/pagevalue"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("PageRoute", result.Controller); + Assert.Equal("ConventionalRoute", result.Action); + + Assert.Equal("pagevalue", result.RouteValues["page"]); + } + } +} \ No newline at end of file diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/test/WebSites/BasicWebSite/BasicWebSite.csproj index fd4b7e9416..2744228b2e 100644 --- a/test/WebSites/BasicWebSite/BasicWebSite.csproj +++ b/test/WebSites/BasicWebSite/BasicWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + + diff --git a/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs b/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs index 2022d5e221..2f2073be06 100644 --- a/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs +++ b/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs @@ -9,14 +9,32 @@ namespace BasicWebSite.Controllers // without affecting view lookups. public class PageRouteController : Controller { + private readonly TestResponseGenerator _generator; + + public PageRouteController(TestResponseGenerator generator) + { + _generator = generator; + } + public IActionResult ConventionalRoute(string page) + { + return _generator.Generate("/PageRoute/ConventionalRoute/" + page); + } + + [HttpGet("/PageRoute/Attribute/{page}")] + public IActionResult AttributeRoute(string page) + { + return _generator.Generate("/PageRoute/Attribute/" + page); + } + + public IActionResult ConventionalRouteView(string page) { ViewData["page"] = page; return View(); } - [HttpGet("/PageRoute/Attribute/{page}")] - public IActionResult AttributeRoute(string page) + [HttpGet("/PageRoute/AttributeView/{page}")] + public IActionResult AttributeRouteView(string page) { ViewData["page"] = page; return View(); diff --git a/test/WebSites/BasicWebSite/Startup.cs b/test/WebSites/BasicWebSite/Startup.cs index 86d5bab150..4cd7646bfb 100644 --- a/test/WebSites/BasicWebSite/Startup.cs +++ b/test/WebSites/BasicWebSite/Startup.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -42,6 +43,8 @@ namespace BasicWebSite services.AddSingleton(); services.AddScoped(); services.AddTransient(); + services.AddScoped(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app) diff --git a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs index 3f3356d7ef..01726c53e5 100644 --- a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs +++ b/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; namespace BasicWebSite @@ -22,6 +23,8 @@ namespace BasicWebSite services.AddHttpContextAccessor(); services.AddScoped(); + services.AddScoped(); + services.AddSingleton(); } public void Configure(IApplicationBuilder app) @@ -35,6 +38,8 @@ namespace BasicWebSite "ActionAsMethod", "{controller}/{action}", defaults: new { controller = "Home", action = "Index" }); + + routes.MapRoute("PageRoute", "{controller}/{action}/{page}"); }); } } diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRoute.cshtml b/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/AttributeRoute.cshtml rename to test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRoute.cshtml b/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRoute.cshtml rename to test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml diff --git a/test/WebSites/VersioningWebSite/TestResponseGenerator.cs b/test/WebSites/Common/TestResponseGenerator.cs similarity index 96% rename from test/WebSites/VersioningWebSite/TestResponseGenerator.cs rename to test/WebSites/Common/TestResponseGenerator.cs index d9ff985c34..69427de69a 100644 --- a/test/WebSites/VersioningWebSite/TestResponseGenerator.cs +++ b/test/WebSites/Common/TestResponseGenerator.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -10,7 +10,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.Extensions.DependencyInjection; -namespace VersioningWebSite +namespace Microsoft.AspNetCore.Mvc { // Generates a response based on the expected URL and action context public class TestResponseGenerator @@ -63,4 +63,4 @@ namespace VersioningWebSite return urlHelper; } } -} \ No newline at end of file +} diff --git a/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs b/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs new file mode 100644 index 0000000000..0c629474f0 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs @@ -0,0 +1,28 @@ +// 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; + +namespace RoutingWebSite +{ + public class PageRouteController + { + private readonly TestResponseGenerator _generator; + + public PageRouteController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult ConventionalRoute(string page) + { + return _generator.Generate("/PageRoute/ConventionalRoute/" + page); + } + + [HttpGet("/PageRoute/Attribute/{page}")] + public IActionResult AttributeRoute(string page) + { + return _generator.Generate("/PageRoute/Attribute/" + page); + } + } +} diff --git a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs index 40d40aa355..c04493b421 100644 --- a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs +++ b/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs @@ -10,11 +10,11 @@ namespace RoutingWebSite { public class RemoveControllerActionDescriptorProvider : IActionDescriptorProvider { - private readonly Type _controllerType; + private readonly ControllerToRemove[] _controllerTypes; - public RemoveControllerActionDescriptorProvider(Type controllerType) + public RemoveControllerActionDescriptorProvider(params ControllerToRemove[] controllerTypes) { - _controllerType = controllerType; + _controllerTypes = controllerTypes; } public int Order => int.MaxValue; @@ -29,12 +29,22 @@ namespace RoutingWebSite { if (item is ControllerActionDescriptor controllerActionDescriptor) { - if (controllerActionDescriptor.ControllerTypeInfo == _controllerType) + var controllerToRemove = _controllerTypes.SingleOrDefault(c => c.ControllerType == controllerActionDescriptor.ControllerTypeInfo); + if (controllerToRemove != null) { - context.Results.Remove(item); + if (controllerToRemove.Actions == null || controllerToRemove.Actions.Contains(controllerActionDescriptor.ActionName)) + { + context.Results.Remove(item); + } } } } } } + + public class ControllerToRemove + { + public Type ControllerType { get; set; } + public string[] Actions { get; set; } + } } \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj index 212a619b2e..a570b1471e 100644 --- a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj +++ b/test/WebSites/RoutingWebSite/RoutingWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + + diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index c00450c9b7..1bf0041a0d 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -58,6 +58,12 @@ namespace RoutingWebSite defaults: new { controller = "Home", action = "Index" }, constraints: new { area = "Travel" }); + routes.MapRoute( + "PageRoute", + "{controller}/{action}/{page}", + defaults: null, + constraints: new { controller = "PageRoute" }); + routes.MapRoute( "ActionAsMethod", "{controller}/{action}", diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 44a6abfc61..e35a9b681a 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -28,7 +28,17 @@ namespace RoutingWebSite // EndpointRoutingController is not compatible with old routing // Remove its action to avoid errors - var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider(typeof(EndpointRoutingController)); + var actionDescriptorProvider = new RemoveControllerActionDescriptorProvider( + new ControllerToRemove + { + ControllerType = typeof(EndpointRoutingController), + Actions = null, // remove all + }, + new ControllerToRemove + { + ControllerType = typeof(PageRouteController), + Actions = new [] { nameof(PageRouteController.AttributeRoute) } + }); services.TryAddEnumerable(ServiceDescriptor.Singleton(actionDescriptorProvider)); } diff --git a/test/WebSites/RoutingWebSite/TestResponseGenerator.cs b/test/WebSites/RoutingWebSite/TestResponseGenerator.cs deleted file mode 100644 index 9b85f04456..0000000000 --- a/test/WebSites/RoutingWebSite/TestResponseGenerator.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Controllers; -using Microsoft.AspNetCore.Mvc.Infrastructure; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace RoutingWebSite -{ - // Generates a response based on the expected URL and action context - public class TestResponseGenerator - { - private readonly ActionContext _actionContext; - private readonly IUrlHelperFactory _urlHelperFactory; - - public TestResponseGenerator(IActionContextAccessor contextAccessor, IUrlHelperFactory urlHelperFactory) - { - _urlHelperFactory = urlHelperFactory; - - _actionContext = contextAccessor.ActionContext; - if (_actionContext == null) - { - throw new InvalidOperationException("ActionContext should not be null here."); - } - } - - public JsonResult Generate(params string[] expectedUrls) - { - var link = (string)null; - var query = _actionContext.HttpContext.Request.Query; - if (query.ContainsKey("link")) - { - var values = query - .Where(kvp => kvp.Key != "link" && kvp.Key != "link_action" && kvp.Key != "link_controller") - .ToDictionary(kvp => kvp.Key.Substring("link_".Length), kvp => (object)kvp.Value[0]); - - var urlHelper = _urlHelperFactory.GetUrlHelper(_actionContext); - link = urlHelper.Action(query["link_action"], query["link_controller"], values); - } - - var attributeRoutingInfo = _actionContext.ActionDescriptor.AttributeRouteInfo; - - return new JsonResult(new - { - expectedUrls = expectedUrls, - actualUrl = _actionContext.HttpContext.Request.Path.Value, - routeName = attributeRoutingInfo == null ? null : attributeRoutingInfo.Name, - routeValues = new Dictionary(_actionContext.RouteData.Values), - - action = ((ControllerActionDescriptor) _actionContext.ActionDescriptor).ActionName, - controller = ((ControllerActionDescriptor)_actionContext.ActionDescriptor).ControllerName, - - link, - }); - } - } -} \ No newline at end of file diff --git a/test/WebSites/VersioningWebSite/VersioningWebSite.csproj b/test/WebSites/VersioningWebSite/VersioningWebSite.csproj index cfda2f9c35..28f147e981 100644 --- a/test/WebSites/VersioningWebSite/VersioningWebSite.csproj +++ b/test/WebSites/VersioningWebSite/VersioningWebSite.csproj @@ -4,6 +4,10 @@ $(StandardTestWebsiteTfms) + + + + From 8ad2da9da336199a13e866f6792a690bf178653a Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 1 Aug 2018 19:52:17 -0700 Subject: [PATCH 269/316] Quick fix: Remove dangling mentions of `dev` branches --- README.md | 2 +- benchmarkapps/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a121f8b427..775ed6bd0c 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ ASP.NET Core MVC **Note: For ASP.NET MVC 5.x, Web API 2.x, and Web Pages 3.x (not ASP.NET Core), see https://github.com/aspnet/AspNetWebStack** -Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=dev)](https://travis-ci.org/aspnet/Mvc) +Travis: [![Travis](https://travis-ci.org/aspnet/Mvc.svg?branch=release/2.2)](https://travis-ci.org/aspnet/Mvc) ASP.NET Core MVC gives you a powerful, patterns-based way to build dynamic websites that enables a clean separation of concerns and gives you full control over markup for enjoyable, agile development. ASP.NET Core MVC includes many features that enable fast, TDD-friendly development for creating sophisticated applications that use the latest web standards. diff --git a/benchmarkapps/README.md b/benchmarkapps/README.md index bf86159bcc..c2e3f97a6d 100644 --- a/benchmarkapps/README.md +++ b/benchmarkapps/README.md @@ -10,6 +10,6 @@ They makes it easier to test local changes than having the App in the Benchmarks 3. If cloned go to the BenchmarksDriver project 4. Use the following command as a guideline for running a test using your changes -`benchmarks --server --client -j https://raw.githubusercontent.com/aspnet/MVC/dev/benchmarkaps/BasicApi/BasicApi.json` +`benchmarks --server --client -j https://raw.githubusercontent.com/aspnet/MVC/{your branch}/benchmarkaps/BasicApi/BasicApi.json` -5. For more info/commands see https://github.com/aspnet/benchmarks/blob/dev/src/BenchmarksDriver/README.md +5. For more info/commands see https://github.com/aspnet/benchmarks/blob/master/src/BenchmarksDriver/README.md From 94101a9cde8e044a2c4fa2595c4926b1b2d151be Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Thu, 4 Oct 2018 17:34:26 +1300 Subject: [PATCH 270/316] Add PageRouteTransformerConvention (#8541) --- .../RouteTokenTransformerConvention.cs | 3 +- .../PageRouteTransformerConvention.cs | 42 ++++++++++++ .../PageActionDescriptorProvider.cs | 37 +++++++++- .../Internal/PageRouteMetadata.cs | 18 +++++ .../Internal/PageRouteModelFactory.cs | 4 ++ .../RouteTokenTransformerConventionTest.cs | 19 ------ .../LinkGeneratorTest.cs | 12 ++++ .../RoutingTestsBase.cs | 32 +++++++++ .../PageRouteTransformerConventionTest.cs | 67 +++++++++++++++++++ .../Controllers/LG1Controller.cs | 8 +++ .../Pages/PageRouteTransformer/Index.cshtml | 3 + .../PageRouteTransformer/TestPage.cshtml | 4 ++ test/WebSites/RoutingWebSite/Startup.cs | 10 +++ .../RoutingWebSite/StartupForLinkGenerator.cs | 10 +++ .../RoutingWebSite/StartupWith21Compat.cs | 10 +++ 15 files changed, 258 insertions(+), 21 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs create mode 100644 test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml create mode 100644 test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs index c83ba6cc47..004e4a22c3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs @@ -8,7 +8,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels { /// /// An that sets attribute routing token replacement - /// to use the specified on selectors. + /// to use the specified on . + /// This convention does not effect Razor page routes. /// public class RouteTokenTransformerConvention : IActionModelConvention { diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs new file mode 100644 index 0000000000..df47d3b7c2 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs @@ -0,0 +1,42 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Routing; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// An that sets page route resolution + /// to use the specified on . + /// This convention does not effect controller action routes. + /// + public class PageRouteTransformerConvention : IPageRouteModelConvention + { + private IOutboundParameterTransformer _parameterTransformer; + + /// + /// Creates a new instance of with the specified . + /// + /// The to use resolve page routes. + public PageRouteTransformerConvention(IOutboundParameterTransformer parameterTransformer) + { + if (parameterTransformer == null) + { + throw new ArgumentNullException(nameof(parameterTransformer)); + } + + _parameterTransformer = parameterTransformer; + } + + public void Apply(PageRouteModel model) + { + if (ShouldApply(model)) + { + model.Properties[typeof(IOutboundParameterTransformer)] = _parameterTransformer; + } + } + + protected virtual bool ShouldApply(PageRouteModel action) => true; + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs index 06bc6f52eb..71a826c857 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs @@ -4,10 +4,15 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; using Microsoft.AspNetCore.Mvc.Routing; +using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure @@ -81,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { Name = selector.AttributeRouteModel.Name, Order = selector.AttributeRouteModel.Order ?? 0, - Template = selector.AttributeRouteModel.Template, + Template = TransformPageRoute(model, selector), SuppressLinkGeneration = selector.AttributeRouteModel.SuppressLinkGeneration, SuppressPathMatching = selector.AttributeRouteModel.SuppressPathMatching, }, @@ -109,5 +114,35 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure actions.Add(descriptor); } } + + private static string TransformPageRoute(PageRouteModel model, SelectorModel selectorModel) + { + model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var transformer); + var pageRouteTransformer = transformer as IOutboundParameterTransformer; + + // Transformer not set on page route + if (pageRouteTransformer == null) + { + return selectorModel.AttributeRouteModel.Template; + } + + var pageRouteMetadata = selectorModel.EndpointMetadata.OfType().SingleOrDefault(); + if (pageRouteMetadata == null) + { + // Selector does not have expected metadata. Should never reach here + throw new InvalidOperationException("Page selector did not have page route metadata."); + } + + var segments = pageRouteMetadata.PageRoute.Split('/'); + for (var i = 0; i < segments.Length; i++) + { + segments[i] = pageRouteTransformer.TransformOutbound(segments[i]); + } + + var transformedPageRoute = string.Join("/", segments); + + // Combine transformed page route with template + return AttributeRouteModel.CombineTemplates(transformedPageRoute, pageRouteMetadata.RouteTemplate); + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs new file mode 100644 index 0000000000..ec5390ea98 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs @@ -0,0 +1,18 @@ +// 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.RazorPages.Internal +{ + // This is used to store the uncombined parts of the final page route + internal class PageRouteMetadata + { + public PageRouteMetadata(string pageRoute, string routeTemplate) + { + PageRoute = pageRoute; + RouteTemplate = routeTemplate; + } + + public string PageRoute { get; } + public string RouteTemplate { get; } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs index e3d2d6289a..63f06ded68 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs @@ -159,6 +159,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal AttributeRouteModel = new AttributeRouteModel { Template = AttributeRouteModel.CombineTemplates(prefix, routeTemplate), + }, + EndpointMetadata = + { + new PageRouteMetadata(prefix, routeTemplate) } }; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs index 807bb4d41a..52214026ef 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs @@ -11,25 +11,6 @@ namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels { public class RouteTokenTransformerConventionTest { - [Fact] - public void Apply_NullAttributeRouteModel_NoOp() - { - // Arrange - var convention = new RouteTokenTransformerConvention(new TestParameterTransformer()); - - var model = new ActionModel(GetMethodInfo(), Array.Empty()); - model.Selectors.Add(new SelectorModel() - { - AttributeRouteModel = null - }); - - // Act - convention.Apply(model); - - // Assert - Assert.Null(model.Selectors[0].AttributeRouteModel); - } - [Fact] public void Apply_HasAttributeRouteModel_SetRouteTokenTransformer() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs index 755a844425..1597aa98a0 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs @@ -158,6 +158,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("/LGPage?another-value=4", responseContent); } + [Fact] + public async Task GetPathByPage_CanGeneratePathToPage_PathTransformed() + { + // Act + var response = await Client.GetAsync("LG1/LinkToPageWithTransformedPath?id=HelloWorld"); + var responseContent = await response.Content.ReadAsStringAsync(); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + Assert.Equal("/page-route-transformer/test-page/ExtraPath/HelloWorld", responseContent); + } + [Fact] public async Task GetPathByPage_CanGeneratePathToPageInArea() { diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index b5b407d4e2..f842256023 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -115,6 +115,38 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Empty(result); } + [Fact] + public async Task Page_PageRouteTransformer() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/page-route-transformer/index"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Page_PageRouteTransformer_WithoutIndex() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/page-route-transformer"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + } + + [Fact] + public async Task Page_PageRouteTransformer_RouteParameter() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/page-route-transformer/test-page/ExtraPath/World"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello from World", body); + } + [Fact] public virtual async Task ConventionalRoutedController_ActionIsReachable() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs new file mode 100644 index 0000000000..1fd04584b7 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs @@ -0,0 +1,67 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Routing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Test.ApplicationModels +{ + public class PageRouteTransformerConventionTest + { + [Fact] + public void Apply_SetTransformer() + { + // Arrange + var transformer = new TestParameterTransformer(); + var convention = new PageRouteTransformerConvention(transformer); + + var model = new PageRouteModel(string.Empty, string.Empty); + + // Act + convention.Apply(model); + + // Assert + Assert.True(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out var routeTokenTransformer)); + Assert.Equal(transformer, routeTokenTransformer); + } + + [Fact] + public void Apply_ShouldApplyFalse_NoOp() + { + // Arrange + var transformer = new TestParameterTransformer(); + var convention = new CustomPageRouteTransformerConvention(transformer); + + var model = new PageRouteModel(string.Empty, string.Empty); + + // Act + convention.Apply(model); + + // Assert + Assert.False(model.Properties.TryGetValue(typeof(IOutboundParameterTransformer), out _)); + } + + private class TestParameterTransformer : IOutboundParameterTransformer + { + public string TransformOutbound(object value) + { + return value?.ToString(); + } + } + + private class CustomPageRouteTransformerConvention : PageRouteTransformerConvention + { + public CustomPageRouteTransformerConvention(IOutboundParameterTransformer parameterTransformer) : base(parameterTransformer) + { + } + + protected override bool ShouldApply(PageRouteModel action) + { + return false; + } + } + } +} diff --git a/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs index f8fb863add..3cef33fa63 100644 --- a/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs +++ b/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs @@ -59,6 +59,14 @@ namespace RoutingWebSite values: QueryToRouteValues(HttpContext.Request.Query)); } + public string LinkToPageWithTransformedPath() + { + return _linkGenerator.GetPathByPage( + HttpContext, + page: "/PageRouteTransformer/TestPage", + values: QueryToRouteValues(HttpContext.Request.Query)); + } + public string LinkToPageInArea() { var values = QueryToRouteValues(HttpContext.Request.Query); diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml new file mode 100644 index 0000000000..dd7e49caa8 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml @@ -0,0 +1,3 @@ +@page +@{ +} diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml new file mode 100644 index 0000000000..5e812c4640 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml @@ -0,0 +1,4 @@ +@page "ExtraPath/{id?}" +@{ +} +Hello from @ViewContext.RouteData.Values["id"] \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 1bf0041a0d..2b3a78b8e6 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -4,6 +4,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Routing; using Microsoft.Extensions.DependencyInjection; @@ -15,6 +16,8 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { + var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer()); + services .AddMvc(options => { @@ -23,6 +26,13 @@ namespace RoutingWebSite typeof(ParameterTransformerController), new SlugifyParameterTransformer())); }) + .AddRazorPagesOptions(options => + { + options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model => + { + pageRouteTransformerConvention.Apply(model); + }); + }) .SetCompatibilityVersion(CompatibilityVersion.Latest); services .AddRouting(options => diff --git a/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs index 3a85de8fda..1d8ff727e7 100644 --- a/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs +++ b/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -13,8 +14,17 @@ namespace RoutingWebSite { public void ConfigureServices(IServiceCollection services) { + var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer()); + services .AddMvc() + .AddRazorPagesOptions(options => + { + options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model => + { + pageRouteTransformerConvention.Apply(model); + }); + }) .SetCompatibilityVersion(CompatibilityVersion.Latest); services .AddRouting(options => diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index e35a9b681a..20800ec3ea 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; @@ -19,8 +20,17 @@ namespace RoutingWebSite // Set up application services public void ConfigureServices(IServiceCollection services) { + var pageRouteTransformerConvention = new PageRouteTransformerConvention(new SlugifyParameterTransformer()); + services .AddMvc() + .AddRazorPagesOptions(options => + { + options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model => + { + pageRouteTransformerConvention.Apply(model); + }); + }) .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); services.AddScoped(); From 153165f9ad03fa735fd566023bb34c1b323d3e54 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 4 Oct 2018 12:25:36 -0700 Subject: [PATCH 271/316] Handle OPTIONS requests without a handler in Razor Pages (#8528) * Handle OPTIONS requests without a handler in Razor Pages Fixes #7438 --- .../DefaultPageApplicationModelProvider.cs | 30 ++-- .../HandleOptionsRequestsPageFilter.cs | 55 +++++++ .../RazorPagesOptions.cs | 42 ++++++ ...gesOptionsConfigureCompatibilityOptions.cs | 5 + .../RazorPagesTest.cs | 48 +++++++ ...DefaultPageApplicationModelProviderTest.cs | 66 +++++++-- .../DisallowOptionsRequestsPageFilterTest.cs | 135 ++++++++++++++++++ ...izationPageApplicationModelProviderTest.cs | 10 +- ...CacheFilterApplicationModelProviderTest.cs | 9 +- .../CompatibilitySwitchIntegrationTest.cs | 4 + .../HelloWorldWithOptionsHandler.cshtml | 17 +++ 11 files changed, 396 insertions(+), 25 deletions(-) rename src/Microsoft.AspNetCore.Mvc.RazorPages/{Internal => ApplicationModels}/DefaultPageApplicationModelProvider.cs (92%) create mode 100644 src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs rename test/Microsoft.AspNetCore.Mvc.RazorPages.Test/{Internal => ApplicationModels}/DefaultPageApplicationModelProviderTest.cs (95%) create mode 100644 test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs create mode 100644 test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs similarity index 92% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs rename to src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs index cc6a02af02..b486d40e8c 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageApplicationModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs @@ -3,36 +3,43 @@ using System; using System.Reflection; -using Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; using Microsoft.Extensions.Internal; using Microsoft.Extensions.Options; +using Resources = Microsoft.AspNetCore.Mvc.RazorPages.Resources; -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +namespace Microsoft.AspNetCore.Mvc.ApplicationModels { - public class DefaultPageApplicationModelProvider : IPageApplicationModelProvider + internal class DefaultPageApplicationModelProvider : IPageApplicationModelProvider { private const string ModelPropertyName = "Model"; private readonly PageHandlerPageFilter _pageHandlerPageFilter = new PageHandlerPageFilter(); private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter(); private readonly IModelMetadataProvider _modelMetadataProvider; - private readonly MvcOptions _options; + private readonly MvcOptions _mvcOptions; + private readonly RazorPagesOptions _razorPagesOptions; private readonly Func _supportsAllRequests; private readonly Func _supportsNonGetRequests; - + private readonly HandleOptionsRequestsPageFilter _handleOptionsRequestsFilter; public DefaultPageApplicationModelProvider( IModelMetadataProvider modelMetadataProvider, - IOptions options) + IOptions options, + IOptions razorPagesOptions) { _modelMetadataProvider = modelMetadataProvider; - _options = options.Value; + _mvcOptions = options.Value; + _razorPagesOptions = razorPagesOptions.Value; _supportsAllRequests = _ => true; - _supportsNonGetRequests = context => !string.Equals(context.HttpContext.Request.Method, "GET", StringComparison.OrdinalIgnoreCase); + _supportsNonGetRequests = context => !HttpMethods.IsGet(context.HttpContext.Request.Method); + _handleOptionsRequestsFilter = new HandleOptionsRequestsPageFilter(); } /// @@ -175,6 +182,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { pageModel.Filters.Add(_pageHandlerResultFilter); } + + if (_razorPagesOptions.AllowDefaultHandlingForOptionsRequests) + { + pageModel.Filters.Add(_handleOptionsRequestsFilter); + } } /// @@ -237,7 +249,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var attributes = parameter.GetCustomAttributes(inherit: true); BindingInfo bindingInfo; - if (_options.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) + if (_mvcOptions.AllowValidatingTopLevelNodes && _modelMetadataProvider is ModelMetadataProvider modelMetadataProviderBase) { var modelMetadata = modelMetadataProviderBase.GetMetadataForParameter(parameter); bindingInfo = BindingInfo.GetBindingInfo(attributes, modelMetadata); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs new file mode 100644 index 0000000000..0c9221e507 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs @@ -0,0 +1,55 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + /// + /// A filter that handles OPTIONS requests page when no handler method is available. + /// + /// a) MVC treats no handler being selected no differently than a page having no handler, both execute the + /// page. + /// b) A common model for programming Razor Pages is to initialize content required by a page in the + /// OnGet handler. Executing a page without running the handler may result in runtime exceptions - + /// e.g. null ref or out of bounds exception if you expected a property or collection to be initialized. + /// + /// + /// Some web crawlers use OPTIONS request when probing servers. In the absence of an uncommon OnOptions + /// handler, executing the page will likely result in runtime errors as described in earlier. This filter + /// attempts to avoid this pit of failure by handling OPTIONS requests and returning a 200 if no handler is selected. + /// + /// + internal sealed class HandleOptionsRequestsPageFilter : IPageFilter, IOrderedFilter + { + /// + /// Ordered to run after filters with default order. + /// + public int Order => 1000; + + public void OnPageHandlerExecuted(PageHandlerExecutedContext context) + { + } + + public void OnPageHandlerExecuting(PageHandlerExecutingContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (context.HandlerMethod == null && + context.Result == null && + HttpMethods.IsOptions(context.HttpContext.Request.Method)) + { + context.Result = new OkResult(); + } + } + + public void OnPageHandlerSelected(PageHandlerSelectedContext context) + { + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs index b3b0b15e11..38bd6f7988 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs @@ -16,6 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { private readonly CompatibilitySwitch _allowAreas; private readonly CompatibilitySwitch _allowMappingHeadRequestsToGetHandler; + private readonly CompatibilitySwitch _allowsDefaultHandlingForOptionsRequests; private readonly ICompatibilitySwitch[] _switches; private string _root = "/Pages"; @@ -24,11 +25,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages { _allowAreas = new CompatibilitySwitch(nameof(AllowAreas)); _allowMappingHeadRequestsToGetHandler = new CompatibilitySwitch(nameof(AllowMappingHeadRequestsToGetHandler)); + _allowsDefaultHandlingForOptionsRequests = new CompatibilitySwitch(nameof(AllowDefaultHandlingForOptionsRequests)); _switches = new ICompatibilitySwitch[] { _allowAreas, _allowMappingHeadRequestsToGetHandler, + _allowsDefaultHandlingForOptionsRequests, }; } @@ -134,6 +137,45 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages set => _allowMappingHeadRequestsToGetHandler.Value = value; } + /// + /// Gets or sets a value that determines if HTTP requests with the OPTIONS method are handled by default, if + /// no handler is available. + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// Razor Pages uses the current request's HTTP method to select a handler method. When no handler is available or selected, + /// the page is immediately executed. This may cause runtime errors if the page relies on the handler method to execute + /// and initialize some state. This setting attempts to avoid this class of error for HTTP OPTIONS requests by + /// returning a 200 OK response. + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired of the value compatibility switch by calling this property's setter will take precedence + /// over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to then + /// this setting will have value true unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have value true unless explicitly configured. + /// + /// + public bool AllowDefaultHandlingForOptionsRequests + { + get => _allowsDefaultHandlingForOptionsRequests.Value; + set => _allowsDefaultHandlingForOptionsRequests.Value = value; + } + IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_switches).GetEnumerator(); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs index 8f5d21d8bc..49596d55b2 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs @@ -29,6 +29,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages values[nameof(RazorPagesOptions.AllowMappingHeadRequestsToGetHandler)] = true; } + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(RazorPagesOptions.AllowDefaultHandlingForOptionsRequests)] = true; + } + return values; } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs index 8aca9a9bcf..ab9c81bfad 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs @@ -1431,6 +1431,54 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPa Assert.Equal("ViewData: Bar", content); } + [Fact] + public async Task OptionsRequest_WithoutHandler_Returns200_WithoutExecutingPage() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Options, "http://localhost/HelloWorld"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content.Trim()); + } + + [Fact] + public async Task PageWithOptionsHandler_ExecutesGetRequest() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Get, "http://localhost/HelloWorldWithOptionsHandler"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello from OnGet!", content.Trim()); + } + + [Fact] + public async Task PageWithOptionsHandler_ExecutesOptionsRequest() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Options, "http://localhost/HelloWorldWithOptionsHandler"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello from OnOptions!", content.Trim()); + } + private async Task AddAntiforgeryHeaders(HttpRequestMessage request) { var getResponse = await Client.GetAsync(request.RequestUri); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs similarity index 95% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs index 2ad90a08a8..7661f32707 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs @@ -6,15 +6,16 @@ using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; +using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; +using Microsoft.AspNetCore.Mvc.RazorPages.Internal; using Microsoft.Extensions.Options; using Xunit; -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +namespace Microsoft.AspNetCore.Mvc.ApplicationModels { public class DefaultPageApplicationModelProviderTest { @@ -887,7 +888,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Arrange var provider = new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), - Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false })); + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }), + Options.Create(new RazorPagesOptions())); var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost)); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); @@ -921,11 +923,11 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { } } - // We're using PropertyHelper from Common to find the properties here, which implements + // We're using PropertyHelper from Common to find the properties here, which implements // out standard set of semantics for properties that the framework interacts with. - // - // One of the desirable consequences of that is we only find 'visible' properties. We're not - // retesting all of the details of PropertyHelper here, just the visibility part as a quick check + // + // One of the desirable consequences of that is we only find 'visible' properties. We're not + // retesting all of the details of PropertyHelper here, just the visibility part as a quick check // that we're using PropertyHelper as expected. [Fact] public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties() @@ -1071,6 +1073,41 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal public void OnGetUser() { } } + [Fact] + public void PopulateFilters_With21CompatBehavior_DoesNotAddDisallowOptionsRequestsPageFilter() + { + // Arrange + var provider = new DefaultPageApplicationModelProvider( + TestModelMetadataProvider.CreateDefaultProvider(), + Options.Create(new MvcOptions()), + Options.Create(new RazorPagesOptions())); + var typeInfo = typeof(object).GetTypeInfo(); + var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); + + // Act + provider.PopulateFilters(pageModel); + + // Assert + Assert.Empty(pageModel.Filters); + } + + [Fact] + public void PopulateFilters_AddsDisallowOptionsRequestsPageFilter() + { + // Arrange + var provider = CreateProvider(); + var typeInfo = typeof(object).GetTypeInfo(); + var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); + + // Act + provider.PopulateFilters(pageModel); + + // Assert + Assert.Collection( + pageModel.Filters, + filter => Assert.IsType(filter)); + } + [Fact] public void PopulateFilters_AddsIFilterMetadataAttributesToModel() { @@ -1085,7 +1122,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection( pageModel.Filters, - filter => Assert.IsType(filter)); + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); } [PageModel] @@ -1109,7 +1147,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection( pageModel.Filters, - filter => Assert.IsType(filter)); + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); } private class ModelImplementingAsyncPageFilter : IAsyncPageFilter @@ -1139,7 +1178,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection( pageModel.Filters, - filter => Assert.IsType(filter)); + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); } private class ModelImplementingPageFilter : IPageFilter @@ -1175,7 +1215,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Collection( pageModel.Filters, filter => Assert.IsType(filter), - filter => Assert.IsType(filter)); + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); } [ServiceFilter(typeof(IServiceProvider))] @@ -1185,7 +1226,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { return new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), - Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true })); + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }), + Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true })); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs new file mode 100644 index 0000000000..a2be81c8c1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs @@ -0,0 +1,135 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Routing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure +{ + public class DisallowOptionsRequestsPageFilterTest + { + [Fact] + public void OnPageHandlerExecuting_DoesNothing_IfHandlerIsSelected() + { + // Arrange + var context = GetContext(new HandlerMethodDescriptor()); + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Null(context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_DoesNotOverwriteResult_IfHandlerIsSelected() + { + // Arrange + var expected = new PageResult(); + var context = GetContext(new HandlerMethodDescriptor()); + context.Result = expected; + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Same(expected, context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_DoesNothing_IfHandlerIsNotSelected_WhenRequestsIsNotOptions() + { + // Arrange + var context = GetContext(handlerMethodDescriptor: null); + context.HttpContext.Request.Method = "PUT"; + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Null(context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_DoesNotOverwriteResult_IfHandlerIsNotSelected_WhenRequestsIsNotOptions() + { + // Arrange + var expected = new PageResult(); + var context = GetContext(handlerMethodDescriptor: null); + context.HttpContext.Request.Method = "DELETE"; + context.Result = expected; + + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Same(expected, context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_DoesNothing_ForOptionsRequestWhenHandlerIsSelected() + { + // Arrange + var context = GetContext(new HandlerMethodDescriptor()); + context.HttpContext.Request.Method = "Options"; + + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Null(context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_DoesNotOverwriteResult_ForOptionsRequestWhenNoHandler() + { + // Arrange + var expected = new NotFoundResult(); + var context = GetContext(new HandlerMethodDescriptor()); + context.Result = expected; + context.HttpContext.Request.Method = "Options"; + + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.Same(expected, context.Result); + } + + [Fact] + public void OnPageHandlerExecuting_SetsResult_ForOptionsRequestWhenNoHandlerIsSelected() + { + // Arrange + var context = GetContext(handlerMethodDescriptor: null); + context.HttpContext.Request.Method = "Options"; + + var filter = new HandleOptionsRequestsPageFilter(); + + // Act + filter.OnPageHandlerExecuting(context); + + // Assert + Assert.IsType(context.Result); + } + + private static PageHandlerExecutingContext GetContext(HandlerMethodDescriptor handlerMethodDescriptor) + { + var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new PageActionDescriptor()); + var pageContext = new PageContext(actionContext); + return new PageHandlerExecutingContext(pageContext, Array.Empty(), handlerMethodDescriptor, new Dictionary(), new object()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs index 008435da3c..559a653985 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.Authorization; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.Options; using Xunit; @@ -30,7 +31,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection( context.PageApplicationModel.Filters, - f => Assert.IsType(f)); + f => Assert.IsType(f), + f => Assert.IsType(f)); } private class PageWithAuthorizeHandlers : Page @@ -63,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Collection( context.PageApplicationModel.Filters, f => Assert.IsType(f), + f => Assert.IsType(f), f => Assert.IsType(f)); } @@ -102,6 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Collection( context.PageApplicationModel.Filters, f => Assert.IsType(f), + f => Assert.IsType(f), f => authorizeFilter = Assert.IsType(f)); // Basic + Basic2 + Derived authorize @@ -143,6 +147,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal Assert.Collection( context.PageApplicationModel.Filters, f => Assert.IsType(f), + f => Assert.IsType(f), f => Assert.IsType(f)); } @@ -163,7 +168,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { var defaultProvider = new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), - Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true })); + Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }), + Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true })); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); defaultProvider.OnProvidersExecuting(context); return context; diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs index cf6acab03f..6b08977e2d 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Moq; @@ -31,7 +32,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal // Assert Assert.Collection( context.PageApplicationModel.Filters, - f => Assert.IsType(f)); + f => Assert.IsType(f), + f => Assert.IsType(f)); } private class PageWithoutResponseCache : Page @@ -66,6 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal context.PageApplicationModel.Filters, f => { }, f => Assert.IsType(f), + f => Assert.IsType(f), f => { var filter = Assert.IsType(f); @@ -112,6 +115,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal context.PageApplicationModel.Filters, f => { }, f => Assert.IsType(f), + f => Assert.IsType(f), f => { var filter = Assert.IsType(f); @@ -139,7 +143,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { var defaultProvider = new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), - Options.Create(new MvcOptions())); + Options.Create(new MvcOptions()), + Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true })); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); defaultProvider.OnProvidersExecuting(context); return context; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 2281f4d602..409605d730 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -49,6 +49,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.True(apiBehaviorOptions.SuppressMapClientErrors); Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); + Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); } [Fact] @@ -80,6 +81,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.True(apiBehaviorOptions.SuppressMapClientErrors); Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); + Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); } [Fact] @@ -111,6 +113,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.False(apiBehaviorOptions.SuppressMapClientErrors); Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); + Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); } [Fact] @@ -142,6 +145,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(apiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses); Assert.False(apiBehaviorOptions.SuppressMapClientErrors); Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); + Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml b/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml new file mode 100644 index 0000000000..9ee557a98e --- /dev/null +++ b/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml @@ -0,0 +1,17 @@ +@page + +@functions { + public string Source { get; set; } + + public void OnGet() + { + Source = "OnGet"; + } + + public void OnOptions(string message) + { + Source = "OnOptions"; + } +} + +Hello from @Source! \ No newline at end of file From 5bddd226a3fde72845a2e2f32dd81172e13d5c9c Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 3 Oct 2018 20:08:14 -0700 Subject: [PATCH 272/316] Use Internal.AspNetCore.Sdk as an MSBuild SDK - should resolve issues with occasional strange MSBuild caching issues in this repo - modeled after aspnet/Scaffolding#905 - follows aspnet/BuildTools#729 recommendation to check in global.config file - see also Microsoft/msbuild#2914 - use newer KoreBuild - `.\build.cmd -update /t:noop` --- .gitignore | 1 - NuGet.config | 7 ++++++- build/dependencies.props | 1 - global.json | 8 ++++++++ korebuild-lock.txt | 4 ++-- src/Directory.Build.props | 1 - .../Microsoft.AspNetCore.Mvc.Abstractions.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Analyzers.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.ApiExplorer.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Core.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Cors.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.DataAnnotations.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Formatters.Json.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Localization.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Razor.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.RazorPages.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.TagHelpers.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Testing.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.ViewFeatures.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.csproj | 2 +- test/Directory.Build.props | 1 - .../Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Core.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Cors.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.FunctionalTests.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.IntegrationTests.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Localization.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Razor.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Test.csproj | 2 +- ...Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj | 2 +- .../Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj | 2 +- test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj | 2 +- test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj | 2 +- 45 files changed, 54 insertions(+), 45 deletions(-) create mode 100644 global.json diff --git a/.gitignore b/.gitignore index e38f0aa101..d5d42e3255 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,6 @@ node_modules *launchSettings.json *.orig .vscode/ -global.json BenchmarkDotNet.Artifacts/ .idea/ msbuild.binlog diff --git a/NuGet.config b/NuGet.config index e32bddfd51..cec9479a72 100644 --- a/NuGet.config +++ b/NuGet.config @@ -2,6 +2,11 @@ - + + diff --git a/build/dependencies.props b/build/dependencies.props index e47c6782ea..c8c3ed64f1 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -17,7 +17,6 @@ 2.1.1.1 2.1.1 2.2.0-preview3-35359 - 2.2.0-preview1-20180928.5 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/global.json b/global.json new file mode 100644 index 0000000000..b5a5299b58 --- /dev/null +++ b/global.json @@ -0,0 +1,8 @@ +{ + "sdk": { + "version": "2.2.100-preview2-009404" + }, + "msbuild-sdks": { + "Internal.AspNetCore.Sdk": "2.2.0-preview2-20181003.2" + } +} diff --git a/korebuild-lock.txt b/korebuild-lock.txt index 26697a21fa..783131ae5e 100644 --- a/korebuild-lock.txt +++ b/korebuild-lock.txt @@ -1,2 +1,2 @@ -version:2.2.0-preview1-20180928.5 -commithash:43faa29f679f47b88689d645b39e6be5e0055d70 +version:2.2.0-preview2-20181003.2 +commithash:41935e62d7853060283c801f49992e2c73a95927 diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ab4e538839..a111c45919 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,7 +2,6 @@ - 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 bea1675a90..34f0f5e6a1 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC abstractions and interfaces for action invocation and dispatching, authorization, action filters, formatters, model binding, routing, validation, and more. diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj index 3030951867..7e796c0673 100644 --- a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj @@ -1,4 +1,4 @@ - + CSharp Analyzers for ASP.NET Core MVC. aspnetcore;aspnetcoremvc diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj index 50b645a854..97ec69ad41 100644 --- a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj @@ -1,4 +1,4 @@ - + CSharp Analyzers for ASP.NET Core MVC. aspnetcore;aspnetcoremvc 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 5ccb12bef4..308a2f225b 100644 --- a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj +++ b/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC API explorer functionality for discovering metadata such as the list of controllers and actions, and their URLs and allowed HTTP methods. 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 0698b3fd85..7a21b34e45 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC core components. Contains common action result types, attribute routing, application model conventions, API explorer, application parts, filters, formatters, model binding, and more. diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj b/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj index f6056e62e5..379eca0738 100644 --- a/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC cross-origin resource sharing (CORS) features. 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 776a31be2d..33df42b291 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC metadata and validation system using System.ComponentModel.DataAnnotations. diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj index 608e370257..45749b70ec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC formatters for JSON input and output and for JSON PATCH input using Json.NET. 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 6458ed6838..18dcb55e3a 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 @@ -1,4 +1,4 @@ - + ASP.NET Core MVC formatters for XML input and output using DataContractSerializer and XmlSerializer. 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 2105530c41..a20bb199aa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC features that enable globalization and localization of applications. 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 b63e6156e9..4bef4f811f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC Razor view engine for CSHTML files. 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 ff5c61f35b..1655f9da77 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC Razor Pages. 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 23fce08ef7..5bd518d2f0 100644 --- a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj +++ b/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC default tag helpers. Contains tag helpers for anchor tags, HTML input elements, caching, scripts, links (for CSS), and more. diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj b/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj index cf0a443e19..058ec2af63 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj @@ -1,4 +1,4 @@ - + Support for writing functional tests for MVC applications. 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 79ef166f49..878ac41bd4 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC view rendering features. Contains common types used in most MVC applications as well as view rendering features such as view engines, views, view components, and HTML helpers. diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj b/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj index ab1d11a599..72cb71f8c2 100644 --- a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj +++ b/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj @@ -1,4 +1,4 @@ - + Provides compatibility in ASP.NET Core MVC with ASP.NET Web API 2 to simplify migration of existing Web API implementations. diff --git a/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj b/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj index 9e57ee217c..9b21543c20 100644 --- a/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj +++ b/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj @@ -1,4 +1,4 @@ - + ASP.NET Core MVC is a web framework that gives you a powerful, patterns-based way to build dynamic websites and web APIs. ASP.NET Core MVC enables a clean separation of concerns and gives you full control over markup. diff --git a/test/Directory.Build.props b/test/Directory.Build.props index 96765456ba..ff13641a43 100644 --- a/test/Directory.Build.props +++ b/test/Directory.Build.props @@ -14,7 +14,6 @@ - diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj index 210346c189..a02016bd2e 100644 --- a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj index 3debef1228..b1ca115faa 100644 --- a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj index aef0c4c3c4..f030903427 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj index 44d8da1323..053be86a52 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj index 684e4dfcef..59269389e9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj index b6673fbd95..14f4f05b2f 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj index 4b93e7b0a3..6536b2ef40 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj index b6673fbd95..14f4f05b2f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj index 3573b91863..d93286ee67 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj @@ -1,4 +1,4 @@ - + diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj index 875d606d3e..be6dd3242f 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj index faf5a9a84a..28b41219a9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj index 2cee7fa108..4e4f07c50f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj index 64835957ec..967a8c3ef4 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj index b9c56ca6de..fd04152330 100644 --- a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj b/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj index 6e674cf924..d0fbd363f4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj b/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj index 23a77e17a1..9ce101241e 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj +++ b/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj index eb45ca869d..1c7b9e362e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj index f098adae0f..4cf0e46940 100644 --- a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj +++ b/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj index 888ee8f077..42bdfc7899 100644 --- a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj +++ b/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj b/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj index 275232d4ab..48a2919472 100644 --- a/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj +++ b/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) diff --git a/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj b/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj index 76dffea5a6..8e9ee75ed7 100644 --- a/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj +++ b/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj @@ -1,4 +1,4 @@ - + $(StandardTestTfms) From 384b814349ff4bcfce36b4e02ad29874e5462eca Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 4 Oct 2018 21:01:06 -0700 Subject: [PATCH 273/316] React to IEndpointSelectorPolicy changes --- build/dependencies.props | 4 +- .../Internal/ActionConstraintCache.cs | 60 +-------------- .../Routing/ActionConstraintMatcherPolicy.cs | 75 ++++++++++++++----- .../Routing/ConsumesMatcherPolicy.cs | 4 +- .../Routing/ConsumesMatcherPolicyTest.cs | 12 +-- .../ActionConstraintMatcherPolicyTest.cs | 8 +- 6 files changed, 72 insertions(+), 91 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index c8c3ed64f1..aca99b4e4b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -47,8 +47,8 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-a-preview3-endpoint-selector-17041 - 2.2.0-a-preview3-endpoint-selector-17041 + 2.2.0-a-preview3-matcher-policy-17051 + 2.2.0-a-preview3-matcher-policy-17051 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs index e8bf938aef..384d48bffa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -150,10 +150,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal { return null; } - + var actionConstraints = new IActionConstraint[count]; var actionConstraintIndex = 0; - for (int i = 0; i < items.Count; i++) + for (var i = 0; i < items.Count; i++) { var actionConstraint = items[i].Constraint; if (actionConstraint != null) @@ -168,7 +168,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal internal class InnerCache { private readonly ActionDescriptorCollection _actions; - private bool? _hasActionConstraints; public InnerCache(ActionDescriptorCollection actions) { @@ -179,61 +178,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal new ConcurrentDictionary(); public int Version => _actions.Version; - - public bool HasActionConstraints - { - get - { - // This is a safe race-condition, since it always transitions from null to non-null. - // All writers will always get the same result. - if (_hasActionConstraints == null) - { - var found = false; - for (var i = 0; i < _actions.Items.Count; i++) - { - var action = _actions.Items[i]; - if (action.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action)) - { - // We need to check for some specific action constraint implementations. - // We've implemented consumes, and HTTP method support inside endpoint routing, so - // we don't need to run an 'action constraint phase' if those are the only constraints. - found = true; - break; - } - } - - _hasActionConstraints = found; - - bool HasSignificantActionConstraint(ActionDescriptor action) - { - for (var i = 0; i < action.ActionConstraints.Count; i++) - { - var actionConstraint = action.ActionConstraints[i]; - if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint)) - { - // This one is OK, we implement this in endpoint routing. - } - else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint") - { - // This one is OK, we implement this in endpoint routing. - } - else if (actionConstraint.GetType() == typeof(ConsumesAttribute)) - { - // This one is OK, we implement this in endpoint routing. - } - else - { - return true; - } - } - - return false; - } - } - - return _hasActionConstraints.Value; - } - } } internal readonly struct CacheEntry diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index 488b15646b..d31e06daf4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Routing { @@ -24,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing // We need to be able to run IActionConstraints on Endpoints that aren't associated // with an action. This is a sentinel value we use when the endpoint isn't from MVC. internal static readonly ActionDescriptor NonAction = new ActionDescriptor(); - + private readonly ActionConstraintCache _actionConstraintCache; public ActionConstraintMatcherPolicy(ActionConstraintCache actionConstraintCache) @@ -35,27 +34,59 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Run really late. public override int Order => 100000; - // Internal for testing - internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints; - - public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) + public bool AppliesToEndpoints(IReadOnlyList endpoints) { - // PERF: we can skip over action constraints if there aren't any app-wide. - // - // Running action constraints (or just checking for them) in a candidate set - // is somewhat expensive compared to other routing operations. This should only - // happen if user-code adds action constraints. - if (ShouldRunActionConstraints) + if (endpoints == null) { - ApplyActionConstraints(httpContext, candidateSet); + throw new ArgumentNullException(nameof(endpoints)); } - return Task.CompletedTask; + // We can skip over action constraints when they aren't any for this set + // of endpoints. This happens once on startup so it removes this component + // from the code path in most scenarios. + for (var i = 0; i < endpoints.Count; i++) + { + var endpoint = endpoints[i]; + var action = endpoint.Metadata.GetMetadata(); + if (action?.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action)) + { + // We need to check for some specific action constraint implementations. + // We've implemented consumes, and HTTP method support inside endpoint routing, so + // we don't need to run an 'action constraint phase' if those are the only constraints. + return true; + } + } + + return false; + + bool HasSignificantActionConstraint(ActionDescriptor a) + { + for (var i = 0; i < a.ActionConstraints.Count; i++) + { + var actionConstraint = a.ActionConstraints[i]; + if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint)) + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint") + { + // This one is OK, we implement this in endpoint routing. + } + else if (actionConstraint.GetType() == typeof(ConsumesAttribute)) + { + // This one is OK, we implement this in endpoint routing. + } + else + { + return true; + } + } + + return false; + } } - - private void ApplyActionConstraints( - HttpContext httpContext, - CandidateSet candidateSet) + + public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) { var finalMatches = EvaluateActionConstraints(httpContext, candidateSet); @@ -74,6 +105,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing candidateSet.SetValidity(finalMatches[i].index, true); } } + + return Task.CompletedTask; } // This is almost the same as the code in ActionSelector, but we can't really share the logic @@ -171,8 +204,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing var endpointsWithConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); var endpointsWithoutConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); - var constraintContext = new ActionConstraintContext(); - constraintContext.Candidates = items.Select(i => i.candidate).ToArray(); + var constraintContext = new ActionConstraintContext + { + Candidates = items.Select(i => i.candidate).ToArray() + }; // Perf: Avoid allocations for (var i = 0; i < items.Count; i++) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs index 65dd1d117b..c0f0dd35bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public IComparer Comparer { get; } = new ConsumesMetadataEndpointComparer(); - public bool AppliesToNode(IReadOnlyList endpoints) + public bool AppliesToEndpoints(IReadOnlyList endpoints) { if (endpoints == null) { @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new ConsumesPolicyJumpTable(exitDestination, ordered); } - private int GetScore(MediaType mediaType) + private int GetScore(in MediaType mediaType) { // Higher score == lower priority - see comments on MediaType. if (mediaType.MatchesAllTypes) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs index cad3edc0b9..b0a59ce678 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public class ConsumesMatcherPolicyTest { [Fact] - public void AppliesToNode_EndpointWithoutMetadata_ReturnsFalse() + public void AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse() { // Arrange var endpoints = new[] { CreateEndpoint("/", null), }; @@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.False(result); } [Fact] - public void AppliesToNode_EndpointWithoutContentTypes_ReturnsFalse() + public void AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse() { // Arrange var endpoints = new[] @@ -42,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.False(result); } [Fact] - public void AppliesToNode_EndpointHasContentTypes_ReturnsTrue() + public void AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue() { // Arrange var endpoints = new[] @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.True(result); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 5210180832..b21072b853 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void ShouldRunActionConstraints_IgnoresIgnorableConstraints() + public void AppliesToEndpoints_IgnoresIgnorableConstraints() { // Arrange var actions = new ActionDescriptor[] @@ -369,11 +369,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing }, }, }; + var endpoints = actions.Select(CreateEndpoint).ToArray(); var selector = CreateSelector(actions); // Act - var result = selector.ShouldRunActionConstraints; + var result = selector.AppliesToEndpoints(endpoints); // Assert Assert.False(result); @@ -397,11 +398,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing }, }, }; + var endpoints = actions.Select(CreateEndpoint).ToArray(); var selector = CreateSelector(actions); // Act - var result = selector.ShouldRunActionConstraints; + var result = selector.AppliesToEndpoints(endpoints); // Assert Assert.True(result); From 67a1f2dda9bcb5795033b0c0ce775d931627fe17 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 5 Oct 2018 21:48:43 -0700 Subject: [PATCH 274/316] Add security text about Host header --- .../IUrlHelper.cs | 30 +++++- .../UrlHelperExtensions.cs | 96 +++++++++++++++++-- 2 files changed, 114 insertions(+), 12 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs index 33a23fef25..8a0a75db9e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs @@ -1,6 +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.Http; using Microsoft.AspNetCore.Mvc.Routing; namespace Microsoft.AspNetCore.Mvc @@ -19,10 +20,18 @@ namespace Microsoft.AspNetCore.Mvc /// Generates a URL with an absolute path for an action method, which contains the action /// name, controller name, route values, protocol to use, host name, and fragment specified by /// . Generates an absolute URL if and - /// are non-null. + /// are non-null. See the remarks section for important security information. /// /// The context object for the generated URLs for an action method. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// string Action(UrlActionContext actionContext); /// @@ -65,19 +74,36 @@ namespace Microsoft.AspNetCore.Mvc /// Generates a URL with an absolute path, which contains the route name, route values, protocol to use, host /// name, and fragment specified by . Generates an absolute URL if /// and are non-null. + /// See the remarks section for important security information. /// /// The context object for the generated URLs for a route. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// string RouteUrl(UrlRouteContext routeContext); /// /// Generates an absolute URL for the specified and route /// , which contains the protocol (such as "http" or "https") and host name from the - /// current request. + /// current request. See the remarks section for important security information. /// /// The name of the route that is used to generate URL. /// An object that contains route values. /// The generated absolute URL. + /// + /// + /// This method uses the value of to populate the host section of the generated URI. + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// validate the Host header in your deployment environment. + /// + /// string Link(string routeName, object values); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs index e790b879ef..40c673da8c 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs @@ -2,9 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Routing; using Microsoft.AspNetCore.Routing; @@ -108,7 +106,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Generates a URL with an absolute path for an action method, which contains the specified /// name, name, route , and - /// to use. + /// to use. See the remarks section for important security information. /// /// The . /// The name of the action method. @@ -116,6 +114,14 @@ namespace Microsoft.AspNetCore.Mvc /// An object that contains route values. /// The protocol for the URL, such as "http" or "https". /// The generated URL. + /// + /// + /// This method uses the value of to populate the host section of the generated URI. + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// validate the Host header in your deployment environment. + /// + /// public static string Action( this IUrlHelper helper, string action, @@ -136,7 +142,7 @@ namespace Microsoft.AspNetCore.Mvc /// name, name, route , /// to use, and name. /// Generates an absolute URL if the and are - /// non-null. + /// non-null. See the remarks section for important security information. /// /// The . /// The name of the action method. @@ -145,6 +151,14 @@ namespace Microsoft.AspNetCore.Mvc /// The protocol for the URL, such as "http" or "https". /// The host name for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string Action( this IUrlHelper helper, string action, @@ -166,7 +180,7 @@ namespace Microsoft.AspNetCore.Mvc /// name, name, route , /// to use, name, and . /// Generates an absolute URL if the and are - /// non-null. + /// non-null. See the remarks section for important security information. /// /// The . /// The name of the action method. @@ -176,6 +190,14 @@ namespace Microsoft.AspNetCore.Mvc /// The host name for the URL. /// The fragment for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string Action( this IUrlHelper helper, string action, @@ -253,13 +275,22 @@ namespace Microsoft.AspNetCore.Mvc /// /// Generates a URL with an absolute path for the specified route and route - /// , which contains the specified to use. + /// , which contains the specified to use. See the + /// remarks section for important security information. /// /// The . /// The name of the route that is used to generate URL. /// An object that contains route values. /// The protocol for the URL, such as "http" or "https". /// The generated URL. + /// + /// + /// This method uses the value of to populate the host section of the generated URI. + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// validate the Host header in your deployment environment. + /// + /// public static string RouteUrl( this IUrlHelper helper, string routeName, @@ -279,6 +310,7 @@ namespace Microsoft.AspNetCore.Mvc /// , which contains the specified to use and /// name. Generates an absolute URL if /// and are non-null. + /// See the remarks section for important security information. /// /// The . /// The name of the route that is used to generate URL. @@ -286,6 +318,14 @@ namespace Microsoft.AspNetCore.Mvc /// The protocol for the URL, such as "http" or "https". /// The host name for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string RouteUrl( this IUrlHelper helper, string routeName, @@ -306,6 +346,7 @@ namespace Microsoft.AspNetCore.Mvc /// , which contains the specified to use, /// name and . Generates an absolute URL if /// and are non-null. + /// See the remarks section for important security information. /// /// The . /// The name of the route that is used to generate URL. @@ -314,6 +355,14 @@ namespace Microsoft.AspNetCore.Mvc /// The host name for the URL. /// The fragment for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string RouteUrl( this IUrlHelper helper, string routeName, @@ -382,7 +431,8 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler, values, protocol: null); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with an absolute path for the specified . See the remarks section + /// for important security information. /// /// The . /// The page name to generate the url for. @@ -390,6 +440,14 @@ namespace Microsoft.AspNetCore.Mvc /// An object that contains route values. /// The protocol for the URL, such as "http" or "https". /// The generated URL. + /// + /// + /// This method uses the value of to populate the host section of the generated URI. + /// Relying on the value of the current request can allow untrusted input to influence the resulting URI unless + /// the Host header has been validated. See the deployment documentation for instructions on how to properly + /// validate the Host header in your deployment environment. + /// + /// public static string Page( this IUrlHelper urlHelper, string pageName, @@ -399,7 +457,8 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler, values, protocol, host: null, fragment: null); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with an absolute path for the specified . See the remarks section for + /// important security information. /// /// The . /// The page name to generate the url for. @@ -408,6 +467,14 @@ namespace Microsoft.AspNetCore.Mvc /// The protocol for the URL, such as "http" or "https". /// The host name for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string Page( this IUrlHelper urlHelper, string pageName, @@ -418,7 +485,8 @@ namespace Microsoft.AspNetCore.Mvc => Page(urlHelper, pageName, pageHandler, values, protocol, host, fragment: null); /// - /// Generates a URL with an absolute path for the specified . + /// Generates a URL with an absolute path for the specified . See the remarks section for + /// important security information. /// /// The . /// The page name to generate the url for. @@ -428,6 +496,14 @@ namespace Microsoft.AspNetCore.Mvc /// The host name for the URL. /// The fragment for the URL. /// The generated URL. + /// + /// + /// The value of should be a trusted value. Relying on the value of the current request + /// can allow untrusted input to influence the resulting URI unless the Host header has been validated. + /// See the deployment documentation for instructions on how to properly validate the Host header in + /// your deployment environment. + /// + /// public static string Page( this IUrlHelper urlHelper, string pageName, From 956441aa68ce7516bce69fda616a0fe060c83882 Mon Sep 17 00:00:00 2001 From: "David J. Quiroga" Date: Mon, 8 Oct 2018 14:41:04 -0300 Subject: [PATCH 275/316] Ignore created URI if Assembly.CodeBase contains a fragment (#8556) * Fixes #8367 --- .../RelatedAssemblyAttribute.cs | 3 +- .../RelatedAssemblyPartTest.cs | 50 ++++++++++++++++++- 2 files changed, 51 insertions(+), 2 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs index b9f6cdb2d7..294d7b9ac8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs @@ -115,7 +115,8 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts internal static string GetAssemblyLocation(Assembly assembly) { - if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && result.IsFile) + if (Uri.TryCreate(assembly.CodeBase, UriKind.Absolute, out var result) && + result.IsFile && string.IsNullOrWhiteSpace(result.Fragment)) { return result.LocalPath; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs index efce8dfaad..6819818bf1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs @@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts { // Arrange var destination = Path.Combine(AssemblyDirectory, "RelatedAssembly.dll"); - var codeBase = "file://x/file/Assembly.dll"; + var codeBase = "file://x:/file/Assembly.dll"; var expected = new Uri(codeBase).LocalPath; var assembly = new TestAssembly { @@ -109,6 +109,54 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationParts Assert.Equal(expected, actual); } + [Fact] + public void GetAssemblyLocation_CodeBase_HasPoundCharacterUnixPath() + { + var destination = Path.Combine(AssemblyDirectory, "RelatedAssembly.dll"); + var expected = @"/etc/#NIN/dotnetcore/tryx/try1.dll"; + var assembly = new TestAssembly + { + CodeBaseSettable = "file:///etc/#NIN/dotnetcore/tryx/try1.dll", + LocationSettable = expected, + }; + + // Act + var actual = RelatedAssemblyAttribute.GetAssemblyLocation(assembly); + Assert.Equal(expected, actual); + } + + [Fact] + public void GetAssemblyLocation_CodeBase_HasPoundCharacterUNCPath() + { + var destination = Path.Combine(AssemblyDirectory, "RelatedAssembly.dll"); + var expected = @"\\server\#NIN\dotnetcore\tryx\try1.dll"; + var assembly = new TestAssembly + { + CodeBaseSettable = "file://server/#NIN/dotnetcore/tryx/try1.dll", + LocationSettable = expected, + }; + + // Act + var actual = RelatedAssemblyAttribute.GetAssemblyLocation(assembly); + Assert.Equal(expected, actual); + } + + [Fact] + public void GetAssemblyLocation_CodeBase_HasPoundCharacterDOSPath() + { + var destination = Path.Combine(AssemblyDirectory, "RelatedAssembly.dll"); + var expected = @"C:\#NIN\dotnetcore\tryx\try1.dll"; + var assembly = new TestAssembly + { + CodeBaseSettable = "file:///C:/#NIN/dotnetcore/tryx/try1.dll", + LocationSettable = expected, + }; + + // Act + var actual = RelatedAssemblyAttribute.GetAssemblyLocation(assembly); + Assert.Equal(expected, actual); + } + private class TestAssembly : Assembly { public override AssemblyName GetName() From 6cee2431f1d5247ea2f5de7af5198f3c328015cc Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Sun, 7 Oct 2018 10:22:38 -0700 Subject: [PATCH 276/316] React to routing changes --- build/dependencies.props | 4 ++-- .../Routing/EndpointRoutingUrlHelperTest.cs | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index aca99b4e4b..4e7f5dcd1f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -47,8 +47,8 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-a-preview3-matcher-policy-17051 - 2.2.0-a-preview3-matcher-policy-17051 + 2.2.0-a-preview3-address-scheme-17059 + 2.2.0-a-preview3-address-scheme-17059 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs index bf423d775e..facb4e07e1 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs @@ -325,7 +325,5 @@ namespace Microsoft.AspNetCore.Mvc.Routing EndpointMetadataCollection.Empty, null); } - - private class SuppressLinkGenerationMetadata : ISuppressLinkGenerationMetadata { } } } From 9daf5ff7a4e092b3a84c060c44148d7d5d40b5c3 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 10 Oct 2018 18:33:30 +1300 Subject: [PATCH 277/316] Fix PageRouteTransformerConvention with custom page routes (#8576) --- .../Infrastructure/PageActionDescriptorProvider.cs | 6 ++++-- .../RoutingTestsBase.cs | 12 ++++++++++++ .../RoutingWebSite/Pages/LGAnotherPage.cshtml | 2 -- test/WebSites/RoutingWebSite/Pages/LGPage.cshtml | 2 -- .../Pages/PageRouteTransformer/Index.cshtml | 2 -- .../PageWithConfiguredRoute.cshtml | 2 ++ .../Pages/PageRouteTransformer/TestPage.cshtml | 2 -- test/WebSites/RoutingWebSite/Startup.cs | 1 + test/WebSites/RoutingWebSite/StartupWith21Compat.cs | 1 + 9 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs index 71a826c857..806a032c35 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs @@ -129,8 +129,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure var pageRouteMetadata = selectorModel.EndpointMetadata.OfType().SingleOrDefault(); if (pageRouteMetadata == null) { - // Selector does not have expected metadata. Should never reach here - throw new InvalidOperationException("Page selector did not have page route metadata."); + // Selector does not have expected metadata + // This selector was likely configured by AddPageRouteModelConvention + // Use the existing explicitly configured template + return selectorModel.AttributeRouteModel.Template; } var segments = pageRouteMetadata.PageRoute.Split('/'); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index f842256023..6ee37de064 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -147,6 +147,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("Hello from World", body); } + [Fact] + public async Task Page_PageRouteTransformer_PageWithConfiguredRoute() + { + // Arrange & Act + var response = await Client.GetAsync("http://localhost/PageRouteTransformer/NewConventionRoute/World"); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + Assert.Equal("Hello from World", body); + } + [Fact] public virtual async Task ConventionalRoutedController_ActionIsReachable() { diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml index d9a0e02177..ab3d7f487c 100644 --- a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml +++ b/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml @@ -1,4 +1,2 @@ @page "{id?}" @model RoutingWebSite.Pages.LGAnotherPageModel -@{ -} diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml index 598b475d1c..f7a24f6983 100644 --- a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml +++ b/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml @@ -1,4 +1,2 @@ @page "{id?}" @model BasicWebSite.Pages.LGPageModel -@{ -} diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml index dd7e49caa8..010bf8732a 100644 --- a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml +++ b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml @@ -1,3 +1 @@ @page -@{ -} diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml new file mode 100644 index 0000000000..f03e3a98e4 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml @@ -0,0 +1,2 @@ +@page "ExtraPath/{id?}" +Hello from @ViewContext.RouteData.Values["id"] \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml index 5e812c4640..f03e3a98e4 100644 --- a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml +++ b/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml @@ -1,4 +1,2 @@ @page "ExtraPath/{id?}" -@{ -} Hello from @ViewContext.RouteData.Values["id"] \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 2b3a78b8e6..03b1fa2ba2 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -28,6 +28,7 @@ namespace RoutingWebSite }) .AddRazorPagesOptions(options => { + options.Conventions.AddPageRoute("/PageRouteTransformer/PageWithConfiguredRoute", "/PageRouteTransformer/NewConventionRoute/{id?}"); options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model => { pageRouteTransformerConvention.Apply(model); diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 20800ec3ea..473653df24 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -26,6 +26,7 @@ namespace RoutingWebSite .AddMvc() .AddRazorPagesOptions(options => { + options.Conventions.AddPageRoute("/PageRouteTransformer/PageWithConfiguredRoute", "/PageRouteTransformer/NewConventionRoute/{id?}"); options.Conventions.AddFolderRouteModelConvention("/PageRouteTransformer", model => { pageRouteTransformerConvention.Apply(model); From 95b4dc8ca031b2f02775f3d84b374ca222e4d7a8 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Mon, 3 Sep 2018 20:08:00 -0700 Subject: [PATCH 278/316] Add first cut of Microsoft.Extensions.ApiDescription.Client package / project - WIP in a number of ways --- Mvc.NoFun.sln | 45 ++ Mvc.sln | 45 ++ build/dependencies.props | 4 + src/GetDocumentInsider/AnsiConsole.cs | 15 + src/GetDocumentInsider/AnsiConstants.cs | 20 + src/GetDocumentInsider/AnsiTextWriter.cs | 131 ++++ src/GetDocumentInsider/CodeAnnotations.cs | 23 + src/GetDocumentInsider/CommandException.cs | 20 + .../CommandLineUtils/CommandArgument.cs | 19 + .../CommandLineApplication.cs | 604 ++++++++++++++++++ .../CommandLineApplicationExtensions.cs | 18 + .../CommandLineUtils/CommandOption.cs | 125 ++++ .../CommandLineUtils/CommandOptionType.cs | 13 + .../CommandParsingException.cs | 15 + .../Commands/CommandBase.cs | 39 ++ .../Commands/GetDocumentCommand.cs | 175 +++++ .../Commands/GetDocumentCommandContext.cs | 24 + .../Commands/GetDocumentCommandWorker.cs | 260 ++++++++ .../Commands/HelpCommandBase.cs | 17 + .../Commands/ProjectCommandBase.cs | 38 ++ .../GetDocumentInsider.csproj | 39 ++ src/GetDocumentInsider/Json.cs | 19 + src/GetDocumentInsider/LogWrapper.cs | 29 + src/GetDocumentInsider/ProductInfo.cs | 24 + src/GetDocumentInsider/Program.cs | 51 ++ .../Properties/Resources.Designer.cs | 207 ++++++ .../Properties/Resources.Designer.tt | 6 + .../Properties/Resources.resx | 192 ++++++ src/GetDocumentInsider/Reporter.cs | 58 ++ src/GetDocumentInsider/WrappedException.cs | 24 + .../DownloadFile.cs | 113 ++++ .../DownloadFileCore.cs | 118 ++++ .../GetFileReferenceMetadata.cs | 95 +++ .../GetProjectReferenceMetadata.cs | 76 +++ .../GetUriReferenceMetadata.cs | 118 ++++ .../ILogWrapper.cs | 50 ++ .../LogWrapper.cs | 35 + ...ft.Extensions.ApiDescription.Client.csproj | 46 ++ ...ft.Extensions.ApiDescription.Client.nuspec | 28 + .../ServiceProjectReferenceMetadata.targets | 32 + .../build/GenerationTasks.props | 133 ++++ .../build/GenerationTasks.targets | 234 +++++++ .../build/NSwagServiceReference.props | 20 + .../build/NSwagServiceReference.targets | 131 ++++ .../GenerationTasks.targets | 29 + .../Commands/InvokeCommand.cs | 241 +++++++ src/dotnet-getdocument/Exe.cs | 118 ++++ src/dotnet-getdocument/Program.cs | 43 ++ src/dotnet-getdocument/Project.cs | 228 +++++++ src/dotnet-getdocument/ProjectOptions.cs | 30 + .../Properties/Resources.Designer.cs | 179 ++++++ .../Properties/Resources.Designer.tt | 6 + .../Properties/Resources.resx | 186 ++++++ .../ServiceProjectReferenceMetadata.props | 17 + .../ServiceProjectReferenceMetadata.targets | 49 ++ .../dotnet-getdocument.csproj | 112 ++++ .../dotnet-getdocument.nuspec | 28 + 57 files changed, 4794 insertions(+) create mode 100644 src/GetDocumentInsider/AnsiConsole.cs create mode 100644 src/GetDocumentInsider/AnsiConstants.cs create mode 100644 src/GetDocumentInsider/AnsiTextWriter.cs create mode 100644 src/GetDocumentInsider/CodeAnnotations.cs create mode 100644 src/GetDocumentInsider/CommandException.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandOption.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs create mode 100644 src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs create mode 100644 src/GetDocumentInsider/Commands/CommandBase.cs create mode 100644 src/GetDocumentInsider/Commands/GetDocumentCommand.cs create mode 100644 src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs create mode 100644 src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs create mode 100644 src/GetDocumentInsider/Commands/HelpCommandBase.cs create mode 100644 src/GetDocumentInsider/Commands/ProjectCommandBase.cs create mode 100644 src/GetDocumentInsider/GetDocumentInsider.csproj create mode 100644 src/GetDocumentInsider/Json.cs create mode 100644 src/GetDocumentInsider/LogWrapper.cs create mode 100644 src/GetDocumentInsider/ProductInfo.cs create mode 100644 src/GetDocumentInsider/Program.cs create mode 100644 src/GetDocumentInsider/Properties/Resources.Designer.cs create mode 100644 src/GetDocumentInsider/Properties/Resources.Designer.tt create mode 100644 src/GetDocumentInsider/Properties/Resources.resx create mode 100644 src/GetDocumentInsider/Reporter.cs create mode 100644 src/GetDocumentInsider/WrappedException.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets create mode 100644 src/dotnet-getdocument/Commands/InvokeCommand.cs create mode 100644 src/dotnet-getdocument/Exe.cs create mode 100644 src/dotnet-getdocument/Program.cs create mode 100644 src/dotnet-getdocument/Project.cs create mode 100644 src/dotnet-getdocument/ProjectOptions.cs create mode 100644 src/dotnet-getdocument/Properties/Resources.Designer.cs create mode 100644 src/dotnet-getdocument/Properties/Resources.Designer.tt create mode 100644 src/dotnet-getdocument/Properties/Resources.resx create mode 100644 src/dotnet-getdocument/ServiceProjectReferenceMetadata.props create mode 100644 src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets create mode 100644 src/dotnet-getdocument/dotnet-getdocument.csproj create mode 100644 src/dotnet-getdocument/dotnet-getdocument.nuspec diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 3995a5463a..0167992a7f 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -117,6 +117,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mvc.Api.Analyzers.Test", "test\Mvc.Api.Analyzers.Test\Mvc.Api.Analyzers.Test.csproj", "{71C626FC-6408-494B-A127-38CB64F71324}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -575,6 +581,42 @@ Global {71C626FC-6408-494B-A127-38CB64F71324}.Release|Mixed Platforms.Build.0 = Release|Any CPU {71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.ActiveCfg = Release|Any CPU {71C626FC-6408-494B-A127-38CB64F71324}.Release|x86.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -619,6 +661,9 @@ Global {92D959F2-66B8-490A-BA33-DA4421EBC948} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {1B398182-9EAE-400B-A2BD-EFFAC0168A36} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {71C626FC-6408-494B-A127-38CB64F71324} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D003597F-372F-4068-A2F0-353BE3C3B39A} diff --git a/Mvc.sln b/Mvc.sln index 775683a5fa..ce10a95245 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -178,6 +178,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Mvc.Ap EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RazorRendering", "benchmarkapps\RazorRendering\RazorRendering.csproj", "{D7C6A696-F232-4288-BCCD-367407E4A934}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\dotnet-getdocument\dotnet-getdocument.csproj", "{4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -938,6 +944,42 @@ Global {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|Mixed Platforms.Build.0 = Release|Any CPU {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.ActiveCfg = Release|Any CPU {D7C6A696-F232-4288-BCCD-367407E4A934}.Release|x86.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Debug|x86.Build.0 = Debug|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Any CPU.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.ActiveCfg = Release|Any CPU + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6}.Release|x86.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.ActiveCfg = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Debug|x86.Build.0 = Debug|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Any CPU.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.ActiveCfg = Release|Any CPU + {2F683CF8-B055-46AE-BF83-9D1307F8D45F}.Release|x86.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.ActiveCfg = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Debug|x86.Build.0 = Debug|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Any CPU.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|Mixed Platforms.Build.0 = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.ActiveCfg = Release|Any CPU + {34E3C302-B767-40C8-B538-3EE2BD4000C4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1010,6 +1052,9 @@ Global {DD7B9F20-354C-4D9E-8C8A-8AE6E7595A87} = {3BA657BF-28B1-42DA-B5B0-1C4601FCF7B1} {3B550487-10E4-4E6D-9CEF-B1B4CA1253DA} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} {D7C6A696-F232-4288-BCCD-367407E4A934} = {2859F266-673A-45A2-9E3C-7B39C6DDD38E} + {4EDC489F-3EC5-4AE3-9841-A285F40F5FF6} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {2F683CF8-B055-46AE-BF83-9D1307F8D45F} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} + {34E3C302-B767-40C8-B538-3EE2BD4000C4} = {32285FA4-6B46-4D6B-A840-2B13E4C8B58E} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {63D344F6-F86D-40E6-85B9-0AABBE338C4A} diff --git a/build/dependencies.props b/build/dependencies.props index 4e7f5dcd1f..304c16413f 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -54,9 +54,11 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 + 2.0.0 2.2.0-preview3-35359 2.2.0-preview3-35359 5.2.6 + 15.6.82 2.8.0 2.8.0 2.2.0-preview3-35359 @@ -100,8 +102,10 @@ 4.7.49 2.0.3 1.0.1 + 11.0.2 4.5.0 4.5.0 + 4.3.2 4.5.1 0.10.0 2.3.1 diff --git a/src/GetDocumentInsider/AnsiConsole.cs b/src/GetDocumentInsider/AnsiConsole.cs new file mode 100644 index 0000000000..30397229aa --- /dev/null +++ b/src/GetDocumentInsider/AnsiConsole.cs @@ -0,0 +1,15 @@ +// 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; + +namespace GetDocument +{ + internal class AnsiConsole + { + public static readonly AnsiTextWriter _out = new AnsiTextWriter(Console.Out); + + public static void WriteLine(string text) + => _out.WriteLine(text); + } +} diff --git a/src/GetDocumentInsider/AnsiConstants.cs b/src/GetDocumentInsider/AnsiConstants.cs new file mode 100644 index 0000000000..e529180983 --- /dev/null +++ b/src/GetDocumentInsider/AnsiConstants.cs @@ -0,0 +1,20 @@ +// 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 GetDocument +{ + internal static class AnsiConstants + { + public const string Reset = "\x1b[22m\x1b[39m"; + public const string Bold = "\x1b[1m"; + public const string Dark = "\x1b[22m"; + public const string Black = "\x1b[30m"; + public const string Red = "\x1b[31m"; + public const string Green = "\x1b[32m"; + public const string Yellow = "\x1b[33m"; + public const string Blue = "\x1b[34m"; + public const string Magenta = "\x1b[35m"; + public const string Cyan = "\x1b[36m"; + public const string Gray = "\x1b[37m"; + } +} diff --git a/src/GetDocumentInsider/AnsiTextWriter.cs b/src/GetDocumentInsider/AnsiTextWriter.cs new file mode 100644 index 0000000000..c8393d4810 --- /dev/null +++ b/src/GetDocumentInsider/AnsiTextWriter.cs @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Text.RegularExpressions; + +namespace GetDocument +{ + internal class AnsiTextWriter + { + private readonly TextWriter _writer; + + public AnsiTextWriter(TextWriter writer) => _writer = writer; + + public void WriteLine(string text) + { + Interpret(text); + _writer.Write(Environment.NewLine); + } + + private void Interpret(string value) + { + var matches = Regex.Matches(value, "\x1b\\[([0-9]+)?m"); + + var start = 0; + foreach (Match match in matches) + { + var length = match.Index - start; + if (length != 0) + { + _writer.Write(value.Substring(start, length)); + } + + Apply(match.Groups[1].Value); + + start = match.Index + match.Length; + } + + if (start != value.Length) + { + _writer.Write(value.Substring(start)); + } + } + + private static void Apply(string parameter) + { + switch (parameter) + { + case "1": + ApplyBold(); + break; + + case "22": + ResetBold(); + break; + + case "30": + ApplyColor(ConsoleColor.Black); + break; + + case "31": + ApplyColor(ConsoleColor.DarkRed); + break; + + case "32": + ApplyColor(ConsoleColor.DarkGreen); + break; + + case "33": + ApplyColor(ConsoleColor.DarkYellow); + break; + + case "34": + ApplyColor(ConsoleColor.DarkBlue); + break; + + case "35": + ApplyColor(ConsoleColor.DarkMagenta); + break; + + case "36": + ApplyColor(ConsoleColor.DarkCyan); + break; + + case "37": + ApplyColor(ConsoleColor.Gray); + break; + + case "39": + ResetColor(); + break; + + default: + Debug.Fail("Unsupported parameter: " + parameter); + break; + } + } + + private static void ApplyBold() + => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor | 8); + + private static void ResetBold() + => Console.ForegroundColor = (ConsoleColor)((int)Console.ForegroundColor & 7); + + private static void ApplyColor(ConsoleColor color) + { + var wasBold = ((int)Console.ForegroundColor & 8) != 0; + + Console.ForegroundColor = color; + + if (wasBold) + { + ApplyBold(); + } + } + + private static void ResetColor() + { + var wasBold = ((int)Console.ForegroundColor & 8) != 0; + + Console.ResetColor(); + + if (wasBold) + { + ApplyBold(); + } + } + } +} diff --git a/src/GetDocumentInsider/CodeAnnotations.cs b/src/GetDocumentInsider/CodeAnnotations.cs new file mode 100644 index 0000000000..7a179f24d3 --- /dev/null +++ b/src/GetDocumentInsider/CodeAnnotations.cs @@ -0,0 +1,23 @@ +// 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; + +namespace JetBrains.Annotations +{ + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field)] + internal sealed class NotNullAttribute : Attribute + { + } + + [AttributeUsage( + AttributeTargets.Method | AttributeTargets.Parameter | + AttributeTargets.Property | AttributeTargets.Delegate | + AttributeTargets.Field)] + internal sealed class CanBeNullAttribute : Attribute + { + } +} diff --git a/src/GetDocumentInsider/CommandException.cs b/src/GetDocumentInsider/CommandException.cs new file mode 100644 index 0000000000..5d9778e61f --- /dev/null +++ b/src/GetDocumentInsider/CommandException.cs @@ -0,0 +1,20 @@ +// 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; + +namespace GetDocument +{ + internal class CommandException : Exception + { + public CommandException(string message) + : base(message) + { + } + + public CommandException(string message, Exception innerException) + : base(message, innerException) + { + } + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs b/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs new file mode 100644 index 0000000000..3346ea0ecb --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs @@ -0,0 +1,19 @@ +// 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; + +namespace Microsoft.DotNet.Cli.CommandLine +{ + internal class CommandArgument + { + public CommandArgument() => Values = new List(); + + public string Name { get; set; } + public string Description { get; set; } + public List Values { get; private set; } + public bool MultipleValues { get; set; } + public string Value => Values.FirstOrDefault(); + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs new file mode 100644 index 0000000000..facbb68ad0 --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs @@ -0,0 +1,604 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.DotNet.Cli.CommandLine +{ + internal class CommandLineApplication + { + private enum ParseOptionResult + { + Succeeded, + ShowHelp, + ShowVersion, + UnexpectedArgs, + } + + // Indicates whether the parser should throw an exception when it runs into an unexpected argument. + // If this field is set to false, the parser will stop parsing when it sees an unexpected argument, and all + // remaining arguments, including the first unexpected argument, will be stored in RemainingArguments property. + private readonly bool _throwOnUnexpectedArg; + + public CommandLineApplication(bool throwOnUnexpectedArg = true) + { + _throwOnUnexpectedArg = throwOnUnexpectedArg; + Options = new List(); + Arguments = new List(); + Commands = new List(); + RemainingArguments = new List(); + Invoke = () => 0; + } + + public CommandLineApplication Parent { get; set; } + public string Name { get; set; } + public string FullName { get; set; } + public string Syntax { get; set; } + public string Description { get; set; } + public List Options { get; private set; } + public CommandOption OptionHelp { get; private set; } + public CommandOption OptionVersion { get; private set; } + public List Arguments { get; private set; } + public List RemainingArguments { get; private set; } + public bool IsShowingInformation { get; protected set; } // Is showing help or version? + public Func Invoke { get; set; } + public Func LongVersionGetter { get; set; } + public Func ShortVersionGetter { get; set; } + public List Commands { get; private set; } + public bool HandleResponseFiles { get; set; } + public bool AllowArgumentSeparator { get; set; } + public bool HandleRemainingArguments { get; set; } + public string ArgumentSeparatorHelpText { get; set; } + + public CommandLineApplication Command(string name, bool throwOnUnexpectedArg = true) + => Command(name, _ => { }, throwOnUnexpectedArg); + + public CommandLineApplication Command(string name, Action configuration, + bool throwOnUnexpectedArg = true) + { + var command = new CommandLineApplication(throwOnUnexpectedArg) { Name = name, Parent = this }; + Commands.Add(command); + configuration(command); + return command; + } + + public CommandOption Option(string template, string description, CommandOptionType optionType) + => Option(template, description, optionType, _ => { }); + + public CommandOption Option(string template, string description, CommandOptionType optionType, Action configuration) + { + var option = new CommandOption(template, optionType) { Description = description }; + Options.Add(option); + configuration(option); + return option; + } + + public CommandArgument Argument(string name, string description, bool multipleValues = false) + => Argument(name, description, _ => { }, multipleValues); + + public CommandArgument Argument(string name, string description, Action configuration, bool multipleValues = false) + { + var lastArg = Arguments.LastOrDefault(); + if (lastArg != null && lastArg.MultipleValues) + { + var message = string.Format("The last argument '{0}' accepts multiple values. No more argument can be added.", + lastArg.Name); + throw new InvalidOperationException(message); + } + + var argument = new CommandArgument { Name = name, Description = description, MultipleValues = multipleValues }; + Arguments.Add(argument); + configuration(argument); + return argument; + } + + public void OnExecute(Func invoke) => Invoke = invoke; + + public void OnExecute(Func> invoke) => Invoke = () => invoke().Result; + + public int Execute(params string[] args) + { + var command = this; + IEnumerator arguments = null; + + if (HandleResponseFiles) + { + args = ExpandResponseFiles(args).ToArray(); + } + + for (var index = 0; index < args.Length; index++) + { + var arg = args[index]; + + var isLongOption = arg.StartsWith("--"); + if (isLongOption || arg.StartsWith("-")) + { + var result = ParseOption(isLongOption, command, args, ref index, out var option); + if (result == ParseOptionResult.ShowHelp) + { + command.ShowHelp(); + return 0; + } + else if (result == ParseOptionResult.ShowVersion) + { + command.ShowVersion(); + return 0; + } + } + else + { + var subcommand = ParseSubCommand(arg, command); + if (subcommand != null) + { + command = subcommand; + } + else + { + if (arguments == null) + { + arguments = new CommandArgumentEnumerator(command.Arguments.GetEnumerator()); + } + + if (arguments.MoveNext()) + { + arguments.Current.Values.Add(arg); + } + else + { + HandleUnexpectedArg(command, args, index, argTypeName: "command or argument"); + } + } + } + } + + return command.Invoke(); + } + + private ParseOptionResult ParseOption( + bool isLongOption, + CommandLineApplication command, + string[] args, + ref int index, + out CommandOption option) + { + option = null; + var result = ParseOptionResult.Succeeded; + var arg = args[index]; + + var optionPrefixLength = isLongOption ? 2 : 1; + var optionComponents = arg.Substring(optionPrefixLength).Split(new[] { ':', '=' }, 2); + var optionName = optionComponents[0]; + + if (isLongOption) + { + option = command.Options.SingleOrDefault( + opt => string.Equals(opt.LongName, optionName, StringComparison.Ordinal)); + } + else + { + option = command.Options.SingleOrDefault( + opt => string.Equals(opt.ShortName, optionName, StringComparison.Ordinal)); + + if (option == null) + { + option = command.Options.SingleOrDefault( + opt => string.Equals(opt.SymbolName, optionName, StringComparison.Ordinal)); + } + } + + if (option == null) + { + if (isLongOption && string.IsNullOrEmpty(optionName) && + !command._throwOnUnexpectedArg && AllowArgumentSeparator) + { + // a stand-alone "--" is the argument separator, so skip it and + // handle the rest of the args as unexpected args + index++; + } + + HandleUnexpectedArg(command, args, index, argTypeName: "option"); + result = ParseOptionResult.UnexpectedArgs; + } + else if (command.OptionHelp == option) + { + result = ParseOptionResult.ShowHelp; + } + else if (command.OptionVersion == option) + { + result = ParseOptionResult.ShowVersion; + } + else + { + if (optionComponents.Length == 2) + { + if (!option.TryParse(optionComponents[1])) + { + command.ShowHint(); + throw new CommandParsingException(command, + $"Unexpected value '{optionComponents[1]}' for option '{optionName}'"); + } + } + else + { + if (option.OptionType == CommandOptionType.NoValue || + option.OptionType == CommandOptionType.BoolValue) + { + // No value is needed for this option + option.TryParse(null); + } + else + { + index++; + arg = args[index]; + if (!option.TryParse(arg)) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unexpected value '{arg}' for option '{optionName}'"); + + } + } + } + } + + return result; + } + + private static CommandLineApplication ParseSubCommand(string arg, CommandLineApplication command) + { + foreach (var subcommand in command.Commands) + { + if (string.Equals(subcommand.Name, arg, StringComparison.OrdinalIgnoreCase)) + { + return subcommand; + } + } + + return null; + } + + // Helper method that adds a help option + public CommandOption HelpOption(string template) + { + // Help option is special because we stop parsing once we see it + // So we store it separately for further use + OptionHelp = Option(template, "Show help information", CommandOptionType.NoValue); + + return OptionHelp; + } + + public CommandOption VersionOption(string template, + string shortFormVersion, + string longFormVersion = null) + { + if (longFormVersion == null) + { + return VersionOption(template, () => shortFormVersion); + } + else + { + return VersionOption(template, () => shortFormVersion, () => longFormVersion); + } + } + + // Helper method that adds a version option + public CommandOption VersionOption(string template, + Func shortFormVersionGetter, + Func longFormVersionGetter = null) + { + // Version option is special because we stop parsing once we see it + // So we store it separately for further use + OptionVersion = Option(template, "Show version information", CommandOptionType.NoValue); + ShortVersionGetter = shortFormVersionGetter; + LongVersionGetter = longFormVersionGetter ?? shortFormVersionGetter; + + return OptionVersion; + } + + // Show short hint that reminds users to use help option + public void ShowHint() + { + if (OptionHelp != null) + { + Console.WriteLine(string.Format("Specify --{0} for a list of available options and commands.", OptionHelp.LongName)); + } + } + + // Show full help + public void ShowHelp(string commandName = null) + { + var headerBuilder = new StringBuilder("Usage:"); + var usagePrefixLength = headerBuilder.Length; + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + if (cmd != this && cmd.Arguments.Any()) + { + var args = string.Join(" ", cmd.Arguments.Select(arg => arg.Name)); + headerBuilder.Insert(usagePrefixLength, string.Format(" {0} {1}", cmd.Name, args)); + } + else + { + headerBuilder.Insert(usagePrefixLength, string.Format(" {0}", cmd.Name)); + } + } + + CommandLineApplication target; + + if (commandName == null || string.Equals(Name, commandName, StringComparison.OrdinalIgnoreCase)) + { + target = this; + } + else + { + target = Commands.SingleOrDefault(cmd => string.Equals(cmd.Name, commandName, StringComparison.OrdinalIgnoreCase)); + + if (target != null) + { + headerBuilder.AppendFormat(" {0}", commandName); + } + else + { + // The command name is invalid so don't try to show help for something that doesn't exist + target = this; + } + + } + + var optionsBuilder = new StringBuilder(); + var commandsBuilder = new StringBuilder(); + var argumentsBuilder = new StringBuilder(); + var argumentSeparatorBuilder = new StringBuilder(); + + var maxArgLen = 0; + for (var cmd = target; cmd != null; cmd = cmd.Parent) + { + if (cmd.Arguments.Any()) + { + if (cmd == target) + { + headerBuilder.Append(" [arguments]"); + } + + if (argumentsBuilder.Length == 0) + { + argumentsBuilder.AppendLine(); + argumentsBuilder.AppendLine("Arguments:"); + } + + maxArgLen = Math.Max(maxArgLen, MaxArgumentLength(cmd.Arguments)); + } + } + + for (var cmd = target; cmd != null; cmd = cmd.Parent) + { + if (cmd.Arguments.Any()) + { + var outputFormat = " {0}{1}"; + foreach (var arg in cmd.Arguments) + { + argumentsBuilder.AppendFormat( + outputFormat, + arg.Name.PadRight(maxArgLen + 2), + arg.Description); + argumentsBuilder.AppendLine(); + } + } + } + + if (target.Options.Any()) + { + headerBuilder.Append(" [options]"); + + optionsBuilder.AppendLine(); + optionsBuilder.AppendLine("Options:"); + var maxOptLen = MaxOptionTemplateLength(target.Options); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxOptLen + 2); + foreach (var opt in target.Options) + { + optionsBuilder.AppendFormat(outputFormat, opt.Template, opt.Description); + optionsBuilder.AppendLine(); + } + } + + if (target.Commands.Any()) + { + headerBuilder.Append(" [command]"); + + commandsBuilder.AppendLine(); + commandsBuilder.AppendLine("Commands:"); + var maxCmdLen = MaxCommandLength(target.Commands); + var outputFormat = string.Format(" {{0, -{0}}}{{1}}", maxCmdLen + 2); + foreach (var cmd in target.Commands.OrderBy(c => c.Name)) + { + commandsBuilder.AppendFormat(outputFormat, cmd.Name, cmd.Description); + commandsBuilder.AppendLine(); + } + + if (OptionHelp != null) + { + commandsBuilder.AppendLine(); + commandsBuilder.AppendFormat("Use \"{0} [command] --help\" for more information about a command.", Name); + commandsBuilder.AppendLine(); + } + } + + if (target.AllowArgumentSeparator || target.HandleRemainingArguments) + { + if (target.AllowArgumentSeparator) + { + headerBuilder.Append(" [[--] ...]]"); + } + else + { + headerBuilder.Append(" [args]"); + } + + if (!string.IsNullOrEmpty(target.ArgumentSeparatorHelpText)) + { + argumentSeparatorBuilder.AppendLine(); + argumentSeparatorBuilder.AppendLine("Args:"); + argumentSeparatorBuilder.AppendLine($" {target.ArgumentSeparatorHelpText}"); + argumentSeparatorBuilder.AppendLine(); + } + } + + headerBuilder.AppendLine(); + + var nameAndVersion = new StringBuilder(); + nameAndVersion.AppendLine(GetFullNameAndVersion()); + nameAndVersion.AppendLine(); + + Console.Write("{0}{1}{2}{3}{4}{5}", nameAndVersion, headerBuilder, argumentsBuilder, optionsBuilder, commandsBuilder, argumentSeparatorBuilder); + } + + public void ShowVersion() + { + for (var cmd = this; cmd != null; cmd = cmd.Parent) + { + cmd.IsShowingInformation = true; + } + + Console.WriteLine(FullName); + Console.WriteLine(LongVersionGetter()); + } + + public string GetFullNameAndVersion() + => ShortVersionGetter == null ? FullName : string.Format("{0} {1}", FullName, ShortVersionGetter()); + + public void ShowRootCommandFullNameAndVersion() + { + var rootCmd = this; + while (rootCmd.Parent != null) + { + rootCmd = rootCmd.Parent; + } + + Console.WriteLine(rootCmd.GetFullNameAndVersion()); + Console.WriteLine(); + } + + private static int MaxOptionTemplateLength(IEnumerable options) + { + var maxLen = 0; + foreach (var opt in options) + { + maxLen = opt.Template.Length > maxLen ? opt.Template.Length : maxLen; + } + return maxLen; + } + + private static int MaxCommandLength(IEnumerable commands) + { + var maxLen = 0; + foreach (var cmd in commands) + { + maxLen = cmd.Name.Length > maxLen ? cmd.Name.Length : maxLen; + } + return maxLen; + } + + private static int MaxArgumentLength(IEnumerable arguments) + { + var maxLen = 0; + foreach (var arg in arguments) + { + maxLen = arg.Name.Length > maxLen ? arg.Name.Length : maxLen; + } + return maxLen; + } + + private static void HandleUnexpectedArg(CommandLineApplication command, string[] args, int index, string argTypeName) + { + if (command._throwOnUnexpectedArg) + { + command.ShowHint(); + throw new CommandParsingException(command, $"Unrecognized {argTypeName} '{args[index]}'"); + } + else + { + command.RemainingArguments.Add(args[index]); + } + } + + private IEnumerable ExpandResponseFiles(IEnumerable args) + { + foreach (var arg in args) + { + if (!arg.StartsWith("@", StringComparison.Ordinal)) + { + yield return arg; + } + else + { + var fileName = arg.Substring(1); + + var responseFileArguments = ParseResponseFile(fileName); + + // ParseResponseFile can suppress expanding this response file by + // returning null. In that case, we'll treat the response + // file token as a regular argument. + + if (responseFileArguments == null) + { + yield return arg; + } + else + { + foreach (var responseFileArgument in responseFileArguments) + { + yield return responseFileArgument.Trim(); + } + } + } + } + } + + private IEnumerable ParseResponseFile(string fileName) + { + if (!HandleResponseFiles) + { + return null; + } + + if (!File.Exists(fileName)) + { + throw new InvalidOperationException($"Response file '{fileName}' doesn't exist."); + } + + return File.ReadLines(fileName); + } + + private class CommandArgumentEnumerator : IEnumerator + { + private readonly IEnumerator _enumerator; + + public CommandArgumentEnumerator(IEnumerator enumerator) => _enumerator = enumerator; + + public CommandArgument Current => _enumerator.Current; + + object IEnumerator.Current => Current; + + public void Dispose() => _enumerator.Dispose(); + + public bool MoveNext() + { + if (Current == null || !Current.MultipleValues) + { + return _enumerator.MoveNext(); + } + + // If current argument allows multiple values, we don't move forward and + // all later values will be added to current CommandArgument.Values + return true; + } + + public void Reset() => _enumerator.Reset(); + } + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs new file mode 100644 index 0000000000..1c43455ee1 --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs @@ -0,0 +1,18 @@ +// 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.DotNet.Cli.CommandLine +{ + internal static class CommandLineApplicationExtensions + { + public static CommandOption Option(this CommandLineApplication command, string template, string description) + => command.Option( + template, + description, + template.IndexOf('<') != -1 + ? template.EndsWith(">...") + ? CommandOptionType.MultipleValue + : CommandOptionType.SingleValue + : CommandOptionType.NoValue); + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs b/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs new file mode 100644 index 0000000000..5ba4b78ae3 --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs @@ -0,0 +1,125 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Microsoft.DotNet.Cli.CommandLine +{ + internal class CommandOption + { + public CommandOption(string template, CommandOptionType optionType) + { + Template = template; + OptionType = optionType; + Values = new List(); + + foreach (var part in Template.Split(new[] { ' ', '|' }, StringSplitOptions.RemoveEmptyEntries)) + { + if (part.StartsWith("--")) + { + LongName = part.Substring(2); + } + else if (part.StartsWith("-")) + { + var optName = part.Substring(1); + + // If there is only one char and it is not an English letter, it is a symbol option (e.g. "-?") + if (optName.Length == 1 && !IsEnglishLetter(optName[0])) + { + SymbolName = optName; + } + else + { + ShortName = optName; + } + } + else if (part.StartsWith("<") && part.EndsWith(">")) + { + ValueName = part.Substring(1, part.Length - 2); + } + else if (optionType == CommandOptionType.MultipleValue && part.StartsWith("<") && part.EndsWith(">...")) + { + ValueName = part.Substring(1, part.Length - 5); + } + else + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + if (string.IsNullOrEmpty(LongName) && string.IsNullOrEmpty(ShortName) && string.IsNullOrEmpty(SymbolName)) + { + throw new ArgumentException($"Invalid template pattern '{template}'", nameof(template)); + } + } + + public string Template { get; set; } + public string ShortName { get; set; } + public string LongName { get; set; } + public string SymbolName { get; set; } + public string ValueName { get; set; } + public string Description { get; set; } + public List Values { get; private set; } + public bool? BoolValue { get; private set; } + public CommandOptionType OptionType { get; private set; } + + public bool TryParse(string value) + { + switch (OptionType) + { + case CommandOptionType.MultipleValue: + Values.Add(value); + break; + case CommandOptionType.SingleValue: + if (Values.Any()) + { + return false; + } + Values.Add(value); + break; + case CommandOptionType.BoolValue: + if (Values.Any()) + { + return false; + } + + if (value == null) + { + // add null to indicate that the option was present, but had no value + Values.Add(null); + BoolValue = true; + } + else + { + if (!bool.TryParse(value, out var boolValue)) + { + return false; + } + + Values.Add(value); + BoolValue = boolValue; + } + break; + case CommandOptionType.NoValue: + if (value != null) + { + return false; + } + // Add a value to indicate that this option was specified + Values.Add("on"); + break; + default: + break; + } + return true; + } + + public bool HasValue() => Values.Any(); + + public string Value() => HasValue() ? Values[0] : null; + + private static bool IsEnglishLetter(char c) => (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs b/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs new file mode 100644 index 0000000000..5f7d37f029 --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.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.DotNet.Cli.CommandLine +{ + internal enum CommandOptionType + { + MultipleValue, + SingleValue, + BoolValue, + NoValue + } +} diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs b/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs new file mode 100644 index 0000000000..c735ecbf12 --- /dev/null +++ b/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs @@ -0,0 +1,15 @@ +// 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; + +namespace Microsoft.DotNet.Cli.CommandLine +{ + internal class CommandParsingException : Exception + { + public CommandParsingException(CommandLineApplication command, string message) + : base(message) => Command = command; + + public CommandLineApplication Command { get; } + } +} diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/GetDocumentInsider/Commands/CommandBase.cs new file mode 100644 index 0000000000..4f66e51d5e --- /dev/null +++ b/src/GetDocumentInsider/Commands/CommandBase.cs @@ -0,0 +1,39 @@ +// 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 GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument.Commands +{ + internal abstract class CommandBase + { + public virtual void Configure(CommandLineApplication command) + { + var verbose = command.Option("-v|--verbose", Resources.VerboseDescription); + var noColor = command.Option("--no-color", Resources.NoColorDescription); + var prefixOutput = command.Option("--prefix-output", Resources.PrefixDescription); + + command.HandleResponseFiles = true; + + command.OnExecute( + () => + { + Reporter.IsVerbose = verbose.HasValue(); + Reporter.NoColor = noColor.HasValue(); + Reporter.PrefixOutput = prefixOutput.HasValue(); + + Validate(); + + return Execute(); + }); + } + + protected virtual void Validate() + { + } + + protected virtual int Execute() + => 0; + } +} diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs new file mode 100644 index 0000000000..f615e4399b --- /dev/null +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -0,0 +1,175 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +#if NETCOREAPP2_0 +using System.Runtime.Loader; +#endif +using GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument.Commands +{ + internal class GetDocumentCommand : ProjectCommandBase + { + internal const string FallbackDocumentName = "v1"; + internal const string FallbackMethod = "Generate"; + internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; + private const string WorkerType = "GetDocument.Commands.GetDocumentCommandWorker"; + + private CommandOption _documentName; + private CommandOption _method; + private CommandOption _output; + private CommandOption _service; + private CommandOption _uri; + + public override void Configure(CommandLineApplication command) + { + base.Configure(command); + + _documentName = command.Option( + "--documentName ", + Resources.DocumentDescription(FallbackDocumentName)); + _method = command.Option("--method ", Resources.MethodDescription(FallbackMethod)); + _output = command.Option("--output ", Resources.OutputDescription); + _service = command.Option("--service ", Resources.ServiceDescription(FallbackService)); + _uri = command.Option("--uri ", Resources.UriDescription); + } + + protected override void Validate() + { + base.Validate(); + + if (!_output.HasValue()) + { + throw new CommandException(Resources.MissingOption(_output.LongName)); + } + + if (_method.HasValue() && !_service.HasValue()) + { + throw new CommandException(Resources.MissingOption(_service.LongName)); + } + + if (_service.HasValue() && !_method.HasValue()) + { + throw new CommandException(Resources.MissingOption(_method.LongName)); + } + } + + protected override int Execute() + { + var thisAssembly = typeof(GetDocumentCommand).Assembly; + + var toolsDirectory = ToolsDirectory.Value(); + var packagedAssemblies = Directory + .EnumerateFiles(toolsDirectory, "*.dll") + .Except(new[] { Path.GetFullPath(thisAssembly.Location) }) + .ToDictionary(path => Path.GetFileNameWithoutExtension(path), path => new AssemblyInfo(path)); + + // Explicitly load all assemblies we need first to preserve target project as much as possible. This + // executable is always run in the target project's context (either through location or .deps.json file). + foreach (var keyValuePair in packagedAssemblies) + { + try + { + keyValuePair.Value.Assembly = Assembly.Load(new AssemblyName(keyValuePair.Key)); + } + catch + { + // Ignore all failures because missing assemblies should be loadable from tools directory. + } + } + +#if NETCOREAPP2_0 + AssemblyLoadContext.Default.Resolving += (loadContext, assemblyName) => + { + var name = assemblyName.Name; + if (!packagedAssemblies.TryGetValue(name, out var info)) + { + return null; + } + + var assemblyPath = info.Path; + if (!File.Exists(assemblyPath)) + { + throw new InvalidOperationException( + $"Referenced assembly '{name}' was not found in '{toolsDirectory}'."); + } + + return loadContext.LoadFromAssemblyPath(assemblyPath); + }; + +#elif NET461 + AppDomain.CurrentDomain.AssemblyResolve += (source, eventArgs) => + { + var assemblyName = new AssemblyName(eventArgs.Name); + var name = assemblyName.Name; + if (!packagedAssemblies.TryGetValue(name, out var info)) + { + return null; + } + + var assembly = info.Assembly; + if (assembly != null) + { + // Loaded already + return assembly; + } + + var assemblyPath = info.Path; + if (!File.Exists(assemblyPath)) + { + throw new InvalidOperationException( + $"Referenced assembly '{name}' was not found in '{toolsDirectory}'."); + } + + return Assembly.LoadFile(assemblyPath); + }; +#else +#error target frameworks need to be updated. +#endif + + // Now safe to reference TestHost type. + try + { + var workerType = thisAssembly.GetType(WorkerType, throwOnError: true); + var methodInfo = workerType.GetMethod("Process", BindingFlags.Public | BindingFlags.Static); + + var assemblyPath = AssemblyPath.Value(); + var context = new GetDocumentCommandContext + { + AssemblyPath = assemblyPath, + AssemblyDirectory = Path.GetDirectoryName(assemblyPath), + AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath), + DocumentName = _documentName.Value(), + Method = _method.Value(), + Output = _output.Value(), + Service = _service.Value(), + Uri = _uri.Value(), + }; + + return (int)methodInfo.Invoke(obj: null, parameters: new[] { context }); + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.ToString()); + return 1; + } + } + + private class AssemblyInfo + { + public AssemblyInfo(string path) + { + Path = path; + } + + public string Path { get; } + + public Assembly Assembly { get; set; } + } + } +} diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs new file mode 100644 index 0000000000..996e9e9701 --- /dev/null +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -0,0 +1,24 @@ +using System; + +namespace GetDocument.Commands +{ + [Serializable] + public class GetDocumentCommandContext + { + public string AssemblyDirectory { get; set; } + + public string AssemblyName { get; set; } + + public string AssemblyPath { get; set; } + + public string DocumentName { get; set; } + + public string Method { get; set; } + + public string Output { get; set; } + + public string Service { get; set; } + + public string Uri { get; set; } + } +} diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs new file mode 100644 index 0000000000..c010165e61 --- /dev/null +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -0,0 +1,260 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Threading; +using System.Threading.Tasks; +using GenerationTasks; +using GetDocument.Properties; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; + +namespace GetDocument.Commands +{ + internal class GetDocumentCommandWorker + { + public static int Process(GetDocumentCommandContext context) + { + var assemblyName = new AssemblyName(context.AssemblyName); + var assembly = Assembly.Load(assemblyName); + var entryPointType = assembly.EntryPoint?.DeclaringType; + if (entryPointType == null) + { + Reporter.WriteError(Resources.MissingEntryPoint(context.AssemblyPath)); + return 2; + } + + var services = GetServices(entryPointType, context.AssemblyPath, context.AssemblyName); + if (services == null) + { + return 3; + } + + var success = TryProcess(context, services); + if (!success && string.IsNullOrEmpty(context.Uri)) + { + return 4; + } + + var builder = GetBuilder(entryPointType, context.AssemblyPath, context.AssemblyName); + if (builder == null) + { + return 5; + } + + // Mute the HttpsRedirectionMiddleware warning about HTTPS configuration. + builder.ConfigureLogging(loggingBuilder => loggingBuilder.AddFilter( + "Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware", + LogLevel.Error)); + + using (var server = new TestServer(builder)) + { + ProcessAsync(context, server).Wait(); + } + + return 0; + } + + public static bool TryProcess(GetDocumentCommandContext context, IServiceProvider services) + { + var documentName = string.IsNullOrEmpty(context.DocumentName) ? + GetDocumentCommand.FallbackDocumentName : + context.DocumentName; + var methodName = string.IsNullOrEmpty(context.Method) ? + GetDocumentCommand.FallbackMethod : + context.Method; + var serviceName = string.IsNullOrEmpty(context.Service) ? + GetDocumentCommand.FallbackService : + context.Service; + + Reporter.WriteInformation(Resources.UsingDocument(documentName)); + Reporter.WriteInformation(Resources.UsingMethod(methodName)); + Reporter.WriteInformation(Resources.UsingService(serviceName)); + + try + { + var serviceType = Type.GetType(serviceName, throwOnError: true); + var method = serviceType.GetMethod(methodName, new[] { typeof(TextWriter), typeof(string) }); + var service = services.GetRequiredService(serviceType); + + var success = true; + using (var writer = File.CreateText(context.Output)) + { + if (method.ReturnType == typeof(bool)) + { + success = (bool)method.Invoke(service, new object[] { writer, documentName }); + } + else + { + method.Invoke(service, new object[] { writer, documentName }); + } + } + + if (!success) + { + var message = Resources.MethodInvocationFailed(methodName, serviceName, documentName); + if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) + { + Reporter.WriteError(message); + } + else + { + Reporter.WriteWarning(message); + } + } + + return success; + } + catch (Exception ex) + { + var message = FormatException(ex); + if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) + { + Reporter.WriteError(message); + } + else + { + Reporter.WriteWarning(message); + } + + return false; + } + } + + public static async Task ProcessAsync(GetDocumentCommandContext context, TestServer server) + { + + Debug.Assert(!string.IsNullOrEmpty(context.Uri)); + Reporter.WriteInformation(Resources.UsingUri(context.Uri)); + + var httpClient = server.CreateClient(); + await DownloadFileCore.DownloadAsync( + context.Uri, + context.Output, + httpClient, + new LogWrapper(), + CancellationToken.None, + timeoutSeconds: 60); + } + + // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. + private static IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName) + { + var args = new[] { Array.Empty() }; + var methodInfo = entryPointType.GetMethod("BuildWebHost"); + if (methodInfo != null) + { + // BuildWebHost (old style has highest priority) + var parameters = methodInfo.GetParameters(); + if (!methodInfo.IsStatic || + parameters.Length != 1 || + typeof(string[]) != parameters[0].ParameterType || + typeof(IWebHost) != methodInfo.ReturnType) + { + Reporter.WriteError( + "BuildWebHost method found in {assemblyPath} does not have expected signature."); + + return null; + } + + try + { + var webHost = (IWebHost)methodInfo.Invoke(obj: null, parameters: args); + + return webHost.Services; + } + catch (Exception ex) + { + Reporter.WriteError($"BuildWebHost method threw: {FormatException(ex)}"); + + return null; + } + } + + if ((methodInfo = entryPointType.GetMethod("CreateWebHostBuilder")) != null) + { + // CreateWebHostBuilder + var parameters = methodInfo.GetParameters(); + if (!methodInfo.IsStatic || + parameters.Length != 1 || + typeof(string[]) != parameters[0].ParameterType || + typeof(IWebHostBuilder) != methodInfo.ReturnType) + { + Reporter.WriteError( + "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature."); + + return null; + } + + try + { + var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args); + + return builder.Build().Services; + } + catch (Exception ex) + { + Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}"); + + return null; + } + } + + // Startup + return new WebHostBuilder().UseStartup(assemblyName).Build().Services; + } + + // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. + private static IWebHostBuilder GetBuilder(Type entryPointType, string assemblyPath, string assemblyName) + { + var methodInfo = entryPointType.GetMethod("BuildWebHost"); + if (methodInfo != null) + { + // BuildWebHost cannot be used. Fall through, most likely to Startup fallback. + Reporter.WriteWarning( + "BuildWebHost method cannot be used. Falling back to minimal Startup configuration."); + } + + methodInfo = entryPointType.GetMethod("CreateWebHostBuilder"); + if (methodInfo != null) + { + // CreateWebHostBuilder + var parameters = methodInfo.GetParameters(); + if (!methodInfo.IsStatic || + parameters.Length != 1 || + typeof(string[]) != parameters[0].ParameterType || + typeof(IWebHostBuilder) != methodInfo.ReturnType) + { + Reporter.WriteError( + "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature."); + + return null; + } + + try + { + var args = new[] { Array.Empty() }; + var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args); + + return builder; + } + catch (Exception ex) + { + Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}"); + + return null; + } + } + + // Startup + return new WebHostBuilder().UseStartup(assemblyName); + } + + private static string FormatException(Exception exception) + { + return $"{exception.GetType().FullName}: {exception.Message}"; + } + } +} diff --git a/src/GetDocumentInsider/Commands/HelpCommandBase.cs b/src/GetDocumentInsider/Commands/HelpCommandBase.cs new file mode 100644 index 0000000000..7e2c89cb5a --- /dev/null +++ b/src/GetDocumentInsider/Commands/HelpCommandBase.cs @@ -0,0 +1,17 @@ +// 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.DotNet.Cli.CommandLine; + +namespace GetDocument.Commands +{ + internal class HelpCommandBase : CommandBase + { + public override void Configure(CommandLineApplication command) + { + base.Configure(command); + + command.HelpOption("-h|--help"); + } + } +} diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs new file mode 100644 index 0000000000..a1a762f96c --- /dev/null +++ b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs @@ -0,0 +1,38 @@ +// 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 GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument.Commands +{ + internal abstract class ProjectCommandBase : HelpCommandBase + { + public CommandOption AssemblyPath { get; private set; } + + public CommandOption ToolsDirectory { get; private set; } + + public override void Configure(CommandLineApplication command) + { + base.Configure(command); + + AssemblyPath = command.Option("-a|--assembly ", Resources.AssemblyDescription); + ToolsDirectory = command.Option("--tools-directory ", Resources.ToolsDirectoryDescription); + } + + protected override void Validate() + { + base.Validate(); + + if (!AssemblyPath.HasValue()) + { + throw new CommandException(Resources.MissingOption(AssemblyPath.LongName)); + } + + if (!ToolsDirectory.HasValue()) + { + throw new CommandException(Resources.MissingOption(ToolsDirectory.LongName)); + } + } + } +} diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj new file mode 100644 index 0000000000..8fe28cd313 --- /dev/null +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -0,0 +1,39 @@ + + + GetDocument.Insider + GetDocument Command-line Tool inside man + false + Exe + GetDocument + netcoreapp2.0;net461 + + + + + + + + + + + + + + + TextTemplatingFileGenerator + Resources.Designer.cs + + + + + + + + + + True + True + Resources.Designer.tt + + + diff --git a/src/GetDocumentInsider/Json.cs b/src/GetDocumentInsider/Json.cs new file mode 100644 index 0000000000..acb62e449f --- /dev/null +++ b/src/GetDocumentInsider/Json.cs @@ -0,0 +1,19 @@ +// 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 GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument +{ + internal static class Json + { + public static CommandOption ConfigureOption(CommandLineApplication command) + => command.Option("--json", Resources.JsonDescription); + + public static string Literal(string text) + => text != null + ? "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" + : "null"; + } +} diff --git a/src/GetDocumentInsider/LogWrapper.cs b/src/GetDocumentInsider/LogWrapper.cs new file mode 100644 index 0000000000..0ab10cb744 --- /dev/null +++ b/src/GetDocumentInsider/LogWrapper.cs @@ -0,0 +1,29 @@ +using System; +using GenerationTasks; + +namespace GetDocument +{ + public class LogWrapper : ILogWrapper + { + public void LogError(string message, params object[] messageArgs) + { + Reporter.WriteError(string.Format(message, messageArgs)); + } + + public void LogError(Exception exception, bool showStackTrace) + { + var message = showStackTrace ? exception.ToString() : exception.Message; + Reporter.WriteError(message); + } + + public void LogInformational(string message, params object[] messageArgs) + { + Reporter.WriteInformation(string.Format(message, messageArgs)); + } + + public void LogWarning(string message, params object[] messageArgs) + { + Reporter.WriteWarning(string.Format(message, messageArgs)); + } + } +} diff --git a/src/GetDocumentInsider/ProductInfo.cs b/src/GetDocumentInsider/ProductInfo.cs new file mode 100644 index 0000000000..22045ee0df --- /dev/null +++ b/src/GetDocumentInsider/ProductInfo.cs @@ -0,0 +1,24 @@ +// 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.Reflection; + +namespace GetDocument +{ + /// + /// This API supports the GetDocument infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static class ProductInfo + { + /// + /// This API supports the GetDocument infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static string GetVersion() + => typeof(ProductInfo) + .Assembly + .GetCustomAttribute() + .InformationalVersion; + } +} diff --git a/src/GetDocumentInsider/Program.cs b/src/GetDocumentInsider/Program.cs new file mode 100644 index 0000000000..935fca4de0 --- /dev/null +++ b/src/GetDocumentInsider/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Text; +using GetDocument.Commands; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument +{ + internal static class Program + { + private static int Main(string[] args) + { + if (Console.IsOutputRedirected) + { + Console.OutputEncoding = Encoding.UTF8; + } + + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + Name = "GetDocument.Insider" + }; + + new GetDocumentCommand().Configure(app); + + try + { + return app.Execute(args); + } + catch (Exception ex) + { + if (ex is CommandException + || ex is CommandParsingException + || (ex is WrappedException wrappedException + && wrappedException.Type == "GetDocument.Design.OperationException")) + { + Reporter.WriteVerbose(ex.ToString()); + } + else + { + Reporter.WriteInformation(ex.ToString()); + } + + Reporter.WriteError(ex.Message); + + return 1; + } + } + } +} diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..90463782ab --- /dev/null +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -0,0 +1,207 @@ +// + +using System; +using System.Reflection; +using System.Resources; +using JetBrains.Annotations; + +namespace GetDocument.Properties +{ + /// + /// This API supports the GetDocument infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The assembly to use. + /// + public static string AssemblyDescription + => GetString("AssemblyDescription"); + + /// + /// Show JSON output. + /// + public static string JsonDescription + => GetString("JsonDescription"); + + /// + /// Missing required option '--{option}'. + /// + public static string MissingOption([CanBeNull] object option) + => string.Format( + GetString("MissingOption", nameof(option)), + option); + + /// + /// Do not colorize output. + /// + public static string NoColorDescription + => GetString("NoColorDescription"); + + /// + /// The file to write the result to. + /// + public static string OutputDescription + => GetString("OutputDescription"); + + /// + /// Prefix console output with logging level. + /// + public static string PrefixDescription + => GetString("PrefixDescription"); + + /// + /// Using application base '{appBase}'. + /// + public static string UsingApplicationBase([CanBeNull] object appBase) + => string.Format( + GetString("UsingApplicationBase", nameof(appBase)), + appBase); + + /// + /// Using assembly '{assembly}'. + /// + public static string UsingAssembly([CanBeNull] object assembly) + => string.Format( + GetString("UsingAssembly", nameof(assembly)), + assembly); + + /// + /// Using configuration file '{config}'. + /// + public static string UsingConfigurationFile([CanBeNull] object config) + => string.Format( + GetString("UsingConfigurationFile", nameof(config)), + config); + + /// + /// Show verbose output. + /// + public static string VerboseDescription + => GetString("VerboseDescription"); + + /// + /// Writing '{file}'... + /// + public static string WritingFile([CanBeNull] object file) + => string.Format( + GetString("WritingFile", nameof(file)), + file); + + /// + /// Using working directory '{workingDirectory}'. + /// + public static string UsingWorkingDirectory([CanBeNull] object workingDirectory) + => string.Format( + GetString("UsingWorkingDirectory", nameof(workingDirectory)), + workingDirectory); + + /// + /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// + public static string ToolsDirectoryDescription + => GetString("ToolsDirectoryDescription"); + + /// + /// The URI to download the document from. + /// + public static string UriDescription + => GetString("UriDescription"); + + /// + /// The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'. + /// + public static string MethodDescription([CanBeNull] object defaultMethod) + => string.Format( + GetString("MethodDescription", nameof(defaultMethod)), + defaultMethod); + + /// + /// The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'. + /// + public static string ServiceDescription([CanBeNull] object defaultService) + => string.Format( + GetString("ServiceDescription", nameof(defaultService)), + defaultService); + + /// + /// Missing required option '--{option1}' or '--{option2}'. + /// + public static string MissingOptions([CanBeNull] object option1, [CanBeNull] object option2) + => string.Format( + GetString("MissingOptions", nameof(option1), nameof(option2)), + option1, option2); + + /// + /// The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'. + /// + public static string DocumentDescription([CanBeNull] object defaultDocumentName) + => string.Format( + GetString("DocumentDescription", nameof(defaultDocumentName)), + defaultDocumentName); + + /// + /// Using document name '{documentName}'. + /// + public static string UsingDocument([CanBeNull] object documentName) + => string.Format( + GetString("UsingDocument", nameof(documentName)), + documentName); + + /// + /// Using method '{method}'. + /// + public static string UsingMethod([CanBeNull] object method) + => string.Format( + GetString("UsingMethod", nameof(method)), + method); + + /// + /// Using service '{service}'. + /// + public static string UsingService([CanBeNull] object service) + => string.Format( + GetString("UsingService", nameof(service)), + service); + + /// + /// Using URI '{uri}'. + /// + public static string UsingUri([CanBeNull] object uri) + => string.Format( + GetString("UsingUri", nameof(uri)), + uri); + + /// + /// Method '{method}' of service '{service}' failed to generate document '{documentName}'. + /// + public static string MethodInvocationFailed([CanBeNull] object method, [CanBeNull] object service, [CanBeNull] object documentName) + => string.Format( + GetString("MethodInvocationFailed", nameof(method), nameof(service), nameof(documentName)), + method, service, documentName); + + /// + /// Assembly '{assemblyPath}' does not contain an entry point. + /// + public static string MissingEntryPoint([CanBeNull] object assemblyPath) + => string.Format( + GetString("MissingEntryPoint", nameof(assemblyPath)), + assemblyPath); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + + return value; + } + } +} + diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.tt b/src/GetDocumentInsider/Properties/Resources.Designer.tt new file mode 100644 index 0000000000..3f636a4db5 --- /dev/null +++ b/src/GetDocumentInsider/Properties/Resources.Designer.tt @@ -0,0 +1,6 @@ +<# + Session["ResourceFile"] = "Resources.resx"; + Session["AccessModifier"] = "internal"; + Session["NoDiagnostics"] = true; +#> +<#@ include file="..\..\tools\Resources.tt" #> diff --git a/src/GetDocumentInsider/Properties/Resources.resx b/src/GetDocumentInsider/Properties/Resources.resx new file mode 100644 index 0000000000..f5c08e3a40 --- /dev/null +++ b/src/GetDocumentInsider/Properties/Resources.resx @@ -0,0 +1,192 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The assembly to use. + + + Show JSON output. + + + Missing required option '--{option}'. + + + Do not colorize output. + + + The file to write the result to. + + + Prefix console output with logging level. + + + Using application base '{appBase}'. + + + Using assembly '{assembly}'. + + + Using configuration file '{config}'. + + + Show verbose output. + + + Writing '{file}'... + + + Using working directory '{workingDirectory}'. + + + Location from which inside man was copied (in the .NET Framework case) or loaded. + + + The URI to download the document from. + + + The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'. + + + The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'. + + + Missing required option '--{option1}' or '--{option2}'. + + + The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'. + + + Using document name '{documentName}'. + + + Using method '{method}'. + + + Using service '{service}'. + + + Using URI '{uri}'. + + + Method '{method}' of service '{service}' failed to generate document '{documentName}'. + + + Assembly '{assemblyPath}' does not contain an entry point. + + \ No newline at end of file diff --git a/src/GetDocumentInsider/Reporter.cs b/src/GetDocumentInsider/Reporter.cs new file mode 100644 index 0000000000..b7c0c264a5 --- /dev/null +++ b/src/GetDocumentInsider/Reporter.cs @@ -0,0 +1,58 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using static GetDocument.AnsiConstants; + +namespace GetDocument +{ + internal static class Reporter + { + public static bool IsVerbose { get; set; } + public static bool NoColor { get; set; } + public static bool PrefixOutput { get; set; } + + public static string Colorize(string value, Func colorizeFunc) + => NoColor ? value : colorizeFunc(value); + + public static void WriteError(string message) + => WriteLine(Prefix("error: ", Colorize(message, x => Bold + Red + x + Reset))); + + public static void WriteWarning(string message) + => WriteLine(Prefix("warn: ", Colorize(message, x => Bold + Yellow + x + Reset))); + + public static void WriteInformation(string message) + => WriteLine(Prefix("info: ", message)); + + public static void WriteData(string message) + => WriteLine(Prefix("data: ", Colorize(message, x => Bold + Gray + x + Reset))); + + public static void WriteVerbose(string message) + { + if (IsVerbose) + { + WriteLine(Prefix("verbose: ", Colorize(message, x => Bold + Black + x + Reset))); + } + } + + private static string Prefix(string prefix, string value) + => PrefixOutput + ? string.Join( + Environment.NewLine, + value.Split(new[] { Environment.NewLine }, StringSplitOptions.None).Select(l => prefix + l)) + : value; + + private static void WriteLine(string value) + { + if (NoColor) + { + Console.WriteLine(value); + } + else + { + AnsiConsole.WriteLine(value); + } + } + } +} diff --git a/src/GetDocumentInsider/WrappedException.cs b/src/GetDocumentInsider/WrappedException.cs new file mode 100644 index 0000000000..7cd7bfc0d3 --- /dev/null +++ b/src/GetDocumentInsider/WrappedException.cs @@ -0,0 +1,24 @@ +// 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; + +namespace GetDocument +{ + internal class WrappedException : Exception + { + private readonly string _stackTrace; + + public WrappedException(string type, string message, string stackTrace) + : base(message) + { + Type = type; + _stackTrace = stackTrace; + } + + public string Type { get; } + + public override string ToString() + => _stackTrace; + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs new file mode 100644 index 0000000000..a1341e0581 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs @@ -0,0 +1,113 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; +using Task = System.Threading.Tasks.Task; +using Utilities = Microsoft.Build.Utilities; + +namespace GenerationTasks +{ + /// + /// Downloads a file. + /// + public class DownloadFile : Utilities.Task, ICancelableTask + { + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + + /// + /// The URI to download. + /// + [Required] + public string Uri { get; set; } + + /// + /// Destination for the downloaded file. If the file already exists, it is not re-downloaded unless + /// is true. + /// + [Required] + public string DestinationPath { get; set; } + + /// + /// Should be overwritten. When true, the file is downloaded and its hash + /// compared to the existing file. If those hashes do not match (or does not + /// exist), is overwritten. + /// + public bool Overwrite { get; set; } + + /// + /// The maximum amount of time in seconds to allow for downloading the file. Defaults to 2 minutes. + /// + public int TimeoutSeconds { get; set; } = 60 * 2; + + /// + public void Cancel() => _cts.Cancel(); + + /// + public override bool Execute() => ExecuteAsync().Result; + + public async Task ExecuteAsync() + { + if (string.IsNullOrEmpty(Uri)) + { + Log.LogError("Uri parameter must not be null or empty."); + return false; + } + + if (string.IsNullOrEmpty(Uri)) + { + Log.LogError("DestinationPath parameter must not be null or empty."); + return false; + } + + var builder = new UriBuilder(Uri); + if (!string.Equals(System.Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) && + !string.Equals(System.Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase)) + { + Log.LogError($"{nameof(Uri)} parameter does not have scheme {System.Uri.UriSchemeHttp} or " + + $"{System.Uri.UriSchemeHttps}."); + return false; + } + + await DownloadFileAsync(Uri, DestinationPath, Overwrite, _cts.Token, TimeoutSeconds, Log); + + return !Log.HasLoggedErrors; + } + + private static async Task DownloadFileAsync( + string uri, + string destinationPath, + bool overwrite, + CancellationToken cancellationToken, + int timeoutSeconds, + TaskLoggingHelper log) + { + var destinationExists = File.Exists(destinationPath); + if (destinationExists && !overwrite) + { + log.LogMessage($"Not downloading '{uri}' to overwrite existing file '{destinationPath}'."); + return; + } + + log.LogMessage($"Downloading '{uri}' to '{destinationPath}'."); + + using (var httpClient = new HttpClient + { + }) + { + await DownloadFileCore.DownloadAsync( + uri, + destinationPath, + httpClient, + new LogWrapper(log), + cancellationToken, + timeoutSeconds); + } + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs new file mode 100644 index 0000000000..bf8156e32f --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs @@ -0,0 +1,118 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Net.Sockets; +using System.Security.Cryptography; +using System.Threading; +using System.Threading.Tasks; + +namespace GenerationTasks +{ + internal static class DownloadFileCore + { + public static async Task DownloadAsync( + string uri, + string destinationPath, + HttpClient httpClient, + ILogWrapper log, + CancellationToken cancellationToken, + int timeoutSeconds) + { + // Timeout if the response has not begun within 1 minute + httpClient.Timeout = TimeSpan.FromMinutes(1); + + var destinationExists = File.Exists(destinationPath); + var reachedCopy = false; + try + { + using (var response = await httpClient.GetAsync(uri, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + cancellationToken.ThrowIfCancellationRequested(); + + using (var responseStreamTask = response.Content.ReadAsStreamAsync()) + { + var finished = await Task.WhenAny( + responseStreamTask, + Task.Delay(TimeSpan.FromSeconds(timeoutSeconds))); + + if (!ReferenceEquals(responseStreamTask, finished)) + { + throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds."); + } + + var responseStream = await responseStreamTask; + if (destinationExists) + { + // Check hashes before using the downloaded information. + var downloadHash = GetHash(responseStream); + responseStream.Position = 0L; + + byte[] destinationHash; + using (var destinationStream = File.OpenRead(destinationPath)) + { + destinationHash = GetHash(destinationStream); + } + + var sameHashes = downloadHash.LongLength == destinationHash.LongLength; + for (var i = 0L; sameHashes && i < downloadHash.LongLength; i++) + { + sameHashes = downloadHash[i] == destinationHash[i]; + } + + if (sameHashes) + { + log.LogInformational($"Not overwriting existing and matching file '{destinationPath}'."); + return; + } + } + else + { + // May need to create directory to hold the file. + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!(string.IsNullOrEmpty(destinationDirectory) || Directory.Exists(destinationDirectory))) + { + Directory.CreateDirectory(destinationDirectory); + } + } + + // Create or overwrite the destination file. + reachedCopy = true; + using (var outStream = File.Create(destinationPath)) + { + responseStream.CopyTo(outStream); + } + } + } + } + catch (HttpRequestException ex) when (destinationExists) + { + if (ex.InnerException is SocketException socketException) + { + log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'."); + } + else + { + log.LogWarning($"Unable to download {uri}: {ex.Message}"); + } + } + catch (Exception ex) + { + log.LogError($"Downloading '{uri}' failed."); + log.LogError(ex, showStackTrace: true); + if (reachedCopy) + { + File.Delete(destinationPath); + } + } + } + + private static byte[] GetHash(Stream stream) + { + using (var algorithm = SHA256.Create()) + { + return algorithm.ComputeHash(stream); + } + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs new file mode 100644 index 0000000000..6ae50f8b21 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace GenerationTasks +{ + /// + /// Adds or corrects Namespace and OutputPath metadata in ServiceFileReference items. + /// + public class GetFileReferenceMetadata : Task + { + /// + /// Default Namespace metadata value for C# output. + /// + [Required] + public string CSharpNamespace { get; set; } + + /// + /// Default directory for OutputPath values. + /// + [Required] + public string OutputDirectory { get; set; } + + /// + /// Default Namespace metadata value for TypeScript output. + /// + [Required] + public string TypeScriptNamespace { get; set; } + + /// + /// The ServiceFileReference items to update. + /// + [Required] + public ITaskItem[] Inputs { get; set; } + + /// + /// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata + /// will contain full paths. + /// + [Output] + public ITaskItem[] Outputs{ get; set; } + + /// + public override bool Execute() + { + var outputs = new List(Inputs.Length); + foreach (var item in Inputs) + { + var newItem = new TaskItem(item); + outputs.Add(newItem); + + var codeGenerator = item.GetMetadata("CodeGenerator"); + var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); + + var @namespace = item.GetMetadata("Namespace"); + if (string.IsNullOrEmpty(@namespace)) + { + @namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace; + newItem.SetMetadata("Namespace", @namespace); + } + + var outputPath = item.GetMetadata("OutputPath"); + if (string.IsNullOrEmpty(outputPath)) + { + var className = item.GetMetadata("ClassName"); + outputPath = className + (isTypeScript ? ".ts" : ".cs"); + } + + outputPath = GetFullPath(outputPath); + newItem.SetMetadata("OutputPath", outputPath); + } + + Outputs = outputs.ToArray(); + + return true; + } + + private string GetFullPath(string path) + { + if (!Path.IsPathRooted(path)) + { + if (!string.IsNullOrEmpty(OutputDirectory)) + { + path = Path.Combine(OutputDirectory, path); + } + + path = Path.GetFullPath(path); + } + + return path; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs new file mode 100644 index 0000000000..e8ac95a819 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace GenerationTasks +{ + /// + /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. + /// + public class GetProjectReferenceMetadata : Task + { + /// + /// Default directory for DocumentPath values. + /// + [Required] + public string DocumentDirectory { get; set; } + + /// + /// The ServiceFileReference items to update. + /// + [Required] + public ITaskItem[] Inputs { get; set; } + + /// + /// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata + /// will contain full paths. + /// + [Output] + public ITaskItem[] Outputs{ get; set; } + + /// + public override bool Execute() + { + var outputs = new List(Inputs.Length); + foreach (var item in Inputs) + { + var newItem = new TaskItem(item); + outputs.Add(newItem); + + var codeGenerator = item.GetMetadata("CodeGenerator"); + var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); + + var outputPath = item.GetMetadata("OutputPath"); + if (string.IsNullOrEmpty(outputPath)) + { + var className = item.GetMetadata("ClassName"); + outputPath = className + (isTypeScript ? ".ts" : ".cs"); + } + + outputPath = GetFullPath(outputPath); + newItem.SetMetadata("OutputPath", outputPath); + } + + Outputs = outputs.ToArray(); + + return true; + } + + private string GetFullPath(string path) + { + if (!Path.IsPathRooted(path)) + { + if (!string.IsNullOrEmpty(DocumentDirectory)) + { + path = Path.Combine(DocumentDirectory, path); + } + + path = Path.GetFullPath(path); + } + + return path; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs new file mode 100644 index 0000000000..7d922e19dc --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -0,0 +1,118 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace GenerationTasks +{ + /// + /// Adds or corrects DocumentPath metadata in ServiceUriReference items. + /// + public class GetUriReferenceMetadata : Task + { + /// + /// Default directory for DocumentPath metadata values. + /// + [Required] + public string DocumentDirectory { get; set; } + + /// + /// The ServiceUriReference items to update. + /// + [Required] + public ITaskItem[] Inputs { get; set; } + + /// + /// The updated ServiceUriReference items. Will include DocumentPath metadata with full paths. + /// + [Output] + public ITaskItem[] Outputs{ get; set; } + + /// + public override bool Execute() + { + var outputs = new List(Inputs.Length); + foreach (var item in Inputs) + { + var newItem = new TaskItem(item); + outputs.Add(newItem); + + var documentPath = item.GetMetadata("DocumentPath"); + if (string.IsNullOrEmpty(documentPath)) + { + var uri = item.ItemSpec; + var builder = new UriBuilder(uri); + if (!builder.Uri.IsAbsoluteUri) + { + Log.LogError($"{nameof(Inputs)} item '{uri}' is not an absolute URI."); + return false; + } + + if (!string.Equals(Uri.UriSchemeHttp, builder.Scheme, StringComparison.OrdinalIgnoreCase) && + !string.Equals(Uri.UriSchemeHttps, builder.Scheme, StringComparison.OrdinalIgnoreCase)) + { + Log.LogError($"{nameof(Inputs)} item '{uri}' does not have scheme {Uri.UriSchemeHttp} or " + + $"{Uri.UriSchemeHttps}."); + return false; + } + + var host = builder.Host + .Replace("/", string.Empty) + .Replace("[", string.Empty) + .Replace("]", string.Empty) + .Replace(':', '_'); + var path = builder.Path + .Replace("!", string.Empty) + .Replace("'", string.Empty) + .Replace("$", string.Empty) + .Replace("%", string.Empty) + .Replace("&", string.Empty) + .Replace("(", string.Empty) + .Replace(")", string.Empty) + .Replace("*", string.Empty) + .Replace("@", string.Empty) + .Replace("~", string.Empty) + .Replace('/', '_') + .Replace(':', '_') + .Replace(';', '_') + .Replace('+', '_') + .Replace('=', '_'); + + documentPath = host + path; + if (char.IsLower(documentPath[0])) + { + documentPath = char.ToUpper(documentPath[0]) + documentPath.Substring(startIndex: 1); + } + + if (!documentPath.EndsWith(".json", StringComparison.OrdinalIgnoreCase)) + { + documentPath = $"{documentPath}.json"; + } + } + + documentPath = GetFullPath(documentPath); + newItem.SetMetadata("DocumentPath", documentPath); + } + + Outputs = outputs.ToArray(); + + return true; + } + + private string GetFullPath(string path) + { + if (!Path.IsPathRooted(path)) + { + if (!string.IsNullOrEmpty(DocumentDirectory)) + { + path = Path.Combine(DocumentDirectory, path); + } + + path = Path.GetFullPath(path); + } + + return path; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs new file mode 100644 index 0000000000..7c773ab359 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs @@ -0,0 +1,50 @@ +using System; + +namespace GenerationTasks +{ + internal interface ILogWrapper + { + /// + /// Logs specified informational . Implementations should be thread safe. + /// + /// The message to log. + /// Optional arguments for formatting the string. + /// + /// Thrown when is . + /// + void LogInformational(string message, params object[] messageArgs); + + /// + /// Logs a warning with the specified . Implementations should be thread safe. + /// + /// The message to log. + /// Optional arguments for formatting the string. + /// + /// Thrown when is . + /// + void LogWarning(string message, params object[] messageArgs); + + /// + /// Logs an error with the specified . Implementations should be thread safe. + /// + /// The message to log. + /// Optional arguments for formatting the string. + /// + /// Thrown when is . + /// + void LogError(string message, params object[] messageArgs); + + /// + /// Logs an error with the message and (optionally) the stack trace of the given . + /// Implementations should be thread safe. + /// + /// The to log. + /// + /// If , append stack trace to 's message. + /// + /// + /// Thrown when is . + /// + void LogError(Exception exception, bool showStackTrace); + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs new file mode 100644 index 0000000000..75ae1407a8 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.Build.Utilities; + +namespace GenerationTasks +{ + internal class LogWrapper : ILogWrapper + { + private readonly TaskLoggingHelper _log; + + public LogWrapper(TaskLoggingHelper log) + { + _log = log; + } + + public void LogError(string message, params object[] messageArgs) + { + _log.LogError(message, messageArgs); + } + + public void LogError(Exception exception, bool showStackTrace) + { + _log.LogErrorFromException(exception, showStackTrace); + } + + public void LogInformational(string message, params object[] messageArgs) + { + _log.LogMessage(message, messageArgs); + } + + public void LogWarning(string message, params object[] messageArgs) + { + _log.LogWarning(message, messageArgs); + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj new file mode 100644 index 0000000000..5dab6ea1aa --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj @@ -0,0 +1,46 @@ + + + + $(GenerateNuspecDependsOn);PopulateNuspec + + + true + + MSBuild tasks and targets for code generation + false + false + $(MSBuildProjectName).nuspec + Build Tasks;msbuild;DownloadFile;GetFilenameFromUri;code generation + GenerationTasks + netstandard2.0;net461 + + + + + + + + + + + + + + + id=$(PackageId); + authors=$(Authors); + configuration=$(Configuration); + copyright=$(Copyright); + description=$(PackageDescription); + iconUrl=$(PackageIconUrl); + licenseUrl=$(PackageLicenseUrl); + owners=$(Company); + projectUrl=$(PackageProjectUrl); + repositoryCommit=$(RepositoryCommit); + repositoryUrl=$(RepositoryUrl); + tags=$(PackageTags.Replace(';', ' ')); + version=$(PackageVersion); + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec new file mode 100644 index 0000000000..5b840bd379 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec @@ -0,0 +1,28 @@ + + + + $id$ + $authors$ + $copyright$ + $description$ + true + $iconUrl$ + $licenseUrl$ + 2.8 + $owners$ + $projectUrl$ + + false + $tags$ + $version$ + + + + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets b/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets new file mode 100644 index 0000000000..5ed88a3f6c --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props new file mode 100644 index 0000000000..dc67dca96a --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props @@ -0,0 +1,133 @@ + + + + + <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 + <_GenerationTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_GenerationTasksAssemblyTarget)/GenerationTasks.dll + <_GenerationTasksAssemblyTarget /> + + + + + + + + true + $([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)')) + + true + $([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)')) + + true + $([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) + $(RootNamespace) + $(RootNamespace) + + + _DefaultDocumentGenerator_GetMetadata; + _DefaultDocumentGenerator_Core; + _DefaultDocumentGenerator_SetMetadata + + + _ServiceProjectReferenceGenerator_GetTargetFramework; + _ServiceProjectReferenceGenerator_GetProjectTargetPath; + _ServiceProjectReferenceGenerator_Restore; + _ServiceProjectReferenceGenerator_Build; + _ServiceProjectReferenceGenerator_Core + + + _ServiceUriReferenceGenerator_GetMetadata; + _ServiceUriReferenceGenerator_Core + + + _CheckServiceReferences; + ServiceProjectReferenceGenerator; + ServiceUriReferenceGenerator; + _ServiceFileReferenceGenerator_GetMetadata; + _ServiceFileReferenceGenerator_Core + + + + + + + + Default + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + %(Filename)Client + + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets new file mode 100644 index 0000000000..7c70d7d279 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets @@ -0,0 +1,234 @@ + + + + + + + + + + + + + + + + <_FullPath>%(ServiceProjectReference.FullPath) + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + <_TargetFrameworks>%(_Temporary.TargetFrameworks) + <_TargetFramework>$(_TargetFrameworks.Split(';')[0]) + + + + $(_TargetFramework) + + <_Temporary Remove="@(_Temporary)" /> + + + + <_FullPath /> + <_TargetFramework /> + <_TargetFrameworks /> + + + + + + + + <_FullPath>%(ServiceProjectReference.FullPath) + <_TargetFramework>%(ServiceProjectReference.TargetFramework) + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + <_ProjectTargetPath>%(_Temporary.FullPath) + + + + $(_ProjectTargetPath) + + <_Temporary Remove="@(_Temporary)" /> + + + + <_FullPath /> + <_ProjectTargetPath /> + <_TargetFramework /> + + + + + + + + + + + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + <_Temporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'Default'))" /> + + + + + + + + + + <_Command>dotnet getdocument --configuration $(Configuration) --no-build + + + <_Temporary Update="@(_Temporary)"> + $(DefaultDocumentGeneratorDefaultOptions) + $(_Command) --project %(FullPath) --output %(DocumentPath) --framework %(TargetFramework) + + <_Temporary Update="@(_Temporary)"> + %(Command) --uri %(_Temporary.Uri) + + <_Temporary Update="@(_Temporary)"> + %(Command) --service %(_Temporary.Service) --method %(_Temporary.Method) + + <_Temporary Update="@(_Temporary)"> + %(Command) %(_Temporary.Options) + + + + + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props new file mode 100644 index 0000000000..4269fdd11c --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props @@ -0,0 +1,20 @@ + + + + + _NSwagDocumentGenerator_GetMetadata; + _NSwagDocumentGenerator_Core; + _NSwagDocumentGenerator_SetMetadata + + + _NSwagCSharpCodeGenerator_GetMetadata; + _NSwagCSharpCodeGenerator_Core; + _NSwagCSharpCodeGenerator_SetMetadata + + + _NSwagTypeScriptCodeGenerator_GetMetadata; + _NSwagTypeScriptCodeGenerator_Core; + _NSwagTypeScriptCodeGenerator_SetMetadata + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets new file mode 100644 index 0000000000..e73cd8d641 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets @@ -0,0 +1,131 @@ + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + <_NSwagTemporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'NSwag'))"> + $(NSwagDocumentGeneratorDefaultOptions) + + + + + + + + <_Command>$(NSwagExe_Core21) aspnetcore2swagger /Configuration:$(Configuration) /NoBuild:true + + + + + + + <_Command /> + + + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + + + + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagCSharp'))"> + $(NSwagCSharpCodeGeneratorDefaultOptions) + + <_NSwagTemporary Update="@(_NSwagTemporary)"> + $(ServiceFileReferenceDirectory)%(ClassName).cs + + + + + + + <_Command>$(NSwagExe_Core21) swagger2csclient /namespace:$(NSwagCSharpCodeGeneratorNamespace) + + + + + + + <_Command /> + + + + + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + + + + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagTypeScript'))"> + $(NSwagTypeScriptCodeGeneratorDefaultOptions) + + <_NSwagTemporary Update="@(_NSwagTemporary)"> + $(ServiceFileReferenceDirectory)%(ClassName).ts + + + + + + + <_Command>$(NSwagExe_Core21) swagger2tsclient /namespace:$(NSwagTypeScriptCodeGeneratorNamespace) + + + + + + + <_Command /> + + + + + + + + + <_NSwagTemporary Remove="@(_NSwagTemporary)" /> + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets new file mode 100644 index 0000000000..5e73fd66e1 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs new file mode 100644 index 0000000000..cf3308ec46 --- /dev/null +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -0,0 +1,241 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.Versioning; +using GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +namespace GetDocument.Commands +{ + internal class InvokeCommand : HelpCommandBase + { + private const string InsideManName = "GetDocument.Insider"; + + private CommandOption _configuration; + private CommandOption _framework; + private CommandOption _msbuildprojectextensionspath; + private CommandOption _output; + private CommandOption _project; + private CommandOption _runtime; + private IList _args; + + public override void Configure(CommandLineApplication command) + { + var options = new ProjectOptions(); + options.Configure(command); + + _project = options.Project; + _framework = options.Framework; + _configuration = options.Configuration; + _runtime = options.Runtime; + _msbuildprojectextensionspath = options.MSBuildProjectExtensionsPath; + + _output = command.Option("--output ", Resources.OutputDescription); + command.VersionOption("--version", ProductInfo.GetVersion); + _args = command.RemainingArguments; + + base.Configure(command); + } + + protected override int Execute() + { + var projectFile = FindProjects( + _project.Value(), + Resources.NoProject, + Resources.MultipleProjects); + Reporter.WriteVerbose(Resources.UsingProject(projectFile)); + + var project = Project.FromFile( + projectFile, + _msbuildprojectextensionspath.Value(), + _framework.Value(), + _configuration.Value(), + _runtime.Value()); + if (!File.Exists(project.AssemblyPath)) + { + throw new CommandException(Resources.MustBuild); + } + + var thisPath = Path.GetFullPath(Path.GetDirectoryName(typeof(InvokeCommand).Assembly.Location)); + + string executable = null; + var cleanupExecutable = false; + try + { + string toolsDirectory; + var args = new List(); + var targetFramework = new FrameworkName(project.TargetFrameworkMoniker); + switch (targetFramework.Identifier) + { + case ".NETFramework": + cleanupExecutable = true; + executable = Path.Combine(project.OutputPath, InsideManName + ".exe"); + toolsDirectory = Path.Combine( + thisPath, + project.PlatformTarget == "x86" ? "net461-x86" : "net461"); + + var executableSource = Path.Combine(toolsDirectory, InsideManName + ".exe"); + File.Copy(executableSource, executable, overwrite: true); + + if (!string.IsNullOrEmpty(project.ConfigPath)) + { + File.Copy(project.ConfigPath, executable + ".config", overwrite: true); + } + break; + + case ".NETCoreApp": + executable = "dotnet"; + toolsDirectory = Path.Combine(thisPath, "netcoreapp2.0"); + + if (targetFramework.Version < new Version(2, 0)) + { + throw new CommandException( + Resources.NETCoreApp1Project(project.Name, targetFramework.Version)); + } + + args.Add("exec"); + args.Add("--depsFile"); + args.Add(project.DepsPath); + + if (!string.IsNullOrEmpty(project.AssetsPath)) + { + using (var reader = new JsonTextReader(File.OpenText(project.AssetsPath))) + { + var projectAssets = JToken.ReadFrom(reader); + var packageFolders = projectAssets["packageFolders"] + .Children() + .Select(p => p.Name); + + foreach (var packageFolder in packageFolders) + { + args.Add("--additionalProbingPath"); + args.Add(packageFolder.TrimEnd(Path.DirectorySeparatorChar)); + } + } + } + + if (File.Exists(project.RuntimeConfigPath)) + { + args.Add("--runtimeConfig"); + args.Add(project.RuntimeConfigPath); + } + else if (!string.IsNullOrEmpty(project.RuntimeFrameworkVersion)) + { + args.Add("--fx-version"); + args.Add(project.RuntimeFrameworkVersion); + } + + args.Add(Path.Combine(toolsDirectory, InsideManName + ".dll")); + break; + + case ".NETStandard": + throw new CommandException(Resources.NETStandardProject(project.Name)); + + default: + throw new CommandException( + Resources.UnsupportedFramework(project.Name, targetFramework.Identifier)); + } + + args.AddRange(_args); + args.Add("--assembly"); + args.Add(project.AssemblyPath); + args.Add("--tools-directory"); + args.Add(toolsDirectory); + + if (!(args.Contains("--method") || string.IsNullOrEmpty(project.DefaultMethod))) + { + args.Add("--method"); + args.Add(project.DefaultMethod); + } + + if (!(args.Contains("--service") || string.IsNullOrEmpty(project.DefaultService))) + { + args.Add("--service"); + args.Add(project.DefaultService); + } + + if (!(args.Contains("--uri") || string.IsNullOrEmpty(project.DefaultUri))) + { + args.Add("--uri"); + args.Add(project.DefaultUri); + } + + if (_output.HasValue()) + { + args.Add("--output"); + args.Add(Path.GetFullPath(_output.Value())); + } + + if (Reporter.IsVerbose) + { + args.Add("--verbose"); + } + + if (Reporter.NoColor) + { + args.Add("--no-color"); + } + + if (Reporter.PrefixOutput) + { + args.Add("--prefix-output"); + } + + return Exe.Run(executable, args, project.Directory); + } + finally + { + if (cleanupExecutable && !string.IsNullOrEmpty(executable)) + { + File.Delete(executable); + File.Delete(executable + ".config"); + } + } + } + + private static string FindProjects( + string path, + string errorWhenNoProject, + string errorWhenMultipleProjects) + { + var specified = true; + if (path == null) + { + specified = false; + path = Directory.GetCurrentDirectory(); + } + else if (!Directory.Exists(path)) // It's not a directory + { + return path; + } + + var projectFiles = Directory + .EnumerateFiles(path, "*.*proj", SearchOption.TopDirectoryOnly) + .Where(f => !string.Equals(Path.GetExtension(f), ".xproj", StringComparison.OrdinalIgnoreCase)) + .Take(2) + .ToList(); + if (projectFiles.Count == 0) + { + throw new CommandException( + specified + ? Resources.NoProjectInDirectory(path) + : errorWhenNoProject); + } + if (projectFiles.Count != 1) + { + throw new CommandException( + specified + ? Resources.MultipleProjectsInDirectory(path) + : errorWhenMultipleProjects); + } + + return projectFiles[0]; + } + } +} diff --git a/src/dotnet-getdocument/Exe.cs b/src/dotnet-getdocument/Exe.cs new file mode 100644 index 0000000000..0c8f7d89ae --- /dev/null +++ b/src/dotnet-getdocument/Exe.cs @@ -0,0 +1,118 @@ +// 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.Diagnostics; +using System.Text; + +namespace GetDocument +{ + internal static class Exe + { + public static int Run( + string executable, + IReadOnlyList args, + string workingDirectory = null, + bool interceptOutput = false) + { + var arguments = ToArguments(args); + + Reporter.WriteVerbose(executable + " " + arguments); + + var startInfo = new ProcessStartInfo + { + FileName = executable, + Arguments = arguments, + UseShellExecute = false, + RedirectStandardOutput = interceptOutput + }; + if (workingDirectory != null) + { + startInfo.WorkingDirectory = workingDirectory; + } + + var process = Process.Start(startInfo); + + if (interceptOutput) + { + string line; + while ((line = process.StandardOutput.ReadLine()) != null) + { + Reporter.WriteVerbose(line); + } + } + + process.WaitForExit(); + + return process.ExitCode; + } + + private static string ToArguments(IReadOnlyList args) + { + var builder = new StringBuilder(); + for (var i = 0; i < args.Count; i++) + { + if (i != 0) + { + builder.Append(" "); + } + + if (args[i].IndexOf(' ') == -1) + { + builder.Append(args[i]); + + continue; + } + + builder.Append("\""); + + var pendingBackslashs = 0; + for (var j = 0; j < args[i].Length; j++) + { + switch (args[i][j]) + { + case '\"': + if (pendingBackslashs != 0) + { + builder.Append('\\', pendingBackslashs * 2); + pendingBackslashs = 0; + } + builder.Append("\\\""); + break; + + case '\\': + pendingBackslashs++; + break; + + default: + if (pendingBackslashs != 0) + { + if (pendingBackslashs == 1) + { + builder.Append("\\"); + } + else + { + builder.Append('\\', pendingBackslashs * 2); + } + + pendingBackslashs = 0; + } + + builder.Append(args[i][j]); + break; + } + } + + if (pendingBackslashs != 0) + { + builder.Append('\\', pendingBackslashs * 2); + } + + builder.Append("\""); + } + + return builder.ToString(); + } + } +} diff --git a/src/dotnet-getdocument/Program.cs b/src/dotnet-getdocument/Program.cs new file mode 100644 index 0000000000..495573a776 --- /dev/null +++ b/src/dotnet-getdocument/Program.cs @@ -0,0 +1,43 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using GetDocument.Commands; +using GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument +{ + internal static class Program + { + private static int Main(string[] args) + { + var app = new CommandLineApplication(throwOnUnexpectedArg: false) + { + FullName = Resources.CommandFullName, + }; + + new InvokeCommand().Configure(app); + + try + { + return app.Execute(args); + } + catch (Exception ex) + { + if (ex is CommandException || ex is CommandParsingException) + { + Reporter.WriteVerbose(ex.ToString()); + } + else + { + Reporter.WriteInformation(ex.ToString()); + } + + Reporter.WriteError(ex.Message); + + return 1; + } + } + } +} diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs new file mode 100644 index 0000000000..274960a08b --- /dev/null +++ b/src/dotnet-getdocument/Project.cs @@ -0,0 +1,228 @@ +// 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.Diagnostics; +using System.IO; +using System.Linq; +using GetDocument.Properties; +using IODirectory = System.IO.Directory; + +namespace GetDocument +{ + internal class Project + { + private const string MSBuildResourceName = "GetDocument.ServiceProjectReferenceMetadata"; + + private Project() + { + } + + public string AssemblyName { get; private set; } + + public string AssemblyPath { get; private set; } + + public string AssetsPath { get; private set; } + + public string Configuration { get; private set; } + + public string ConfigPath { get; private set; } + + public string DefaultDocumentName { get; private set; } + + public string DefaultMethod { get; private set; } + + public string DefaultService { get; private set; } + + public string DefaultUri { get; private set; } + + public string DepsPath { get; private set; } + + public string Directory { get; private set; } + + public string ExtensionsPath { get; private set; } + + public string Name { get; private set; } + + public string OutputPath { get; private set; } + + public string Platform { get; private set; } + + public string PlatformTarget { get; private set; } + + public string RuntimeConfigPath { get; private set; } + + public string RuntimeFrameworkVersion { get; private set; } + + public string TargetFramework { get; private set; } + + public string TargetFrameworkMoniker { get; private set; } + + public static Project FromFile( + string file, + string buildExtensionsDirectory, + string framework = null, + string configuration = null, + string runtime = null) + { + Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty."); + + if (string.IsNullOrEmpty(buildExtensionsDirectory)) + { + buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(file), "obj"); + } + + IODirectory.CreateDirectory(buildExtensionsDirectory); + + var assembly = typeof(Project).Assembly; + var propsPath = Path.Combine( + buildExtensionsDirectory, + Path.GetFileName(file) + ".ServiceProjectReferenceMetadata.props"); + using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.props")) + { + using (var output = File.OpenWrite(propsPath)) + { + Reporter.WriteVerbose(Resources.WritingFile(propsPath)); + input.CopyTo(output); + } + } + + var targetsPath = Path.ChangeExtension(propsPath, ".targets"); + using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.targets")) + { + using (var output = File.OpenWrite(targetsPath)) + { + // NB: Copy always in case it changes + Reporter.WriteVerbose(Resources.WritingFile(targetsPath)); + input.CopyTo(output); + } + } + + IDictionary metadata; + var metadataPath = Path.GetTempFileName(); + try + { + var propertyArg = "/property:ServiceProjectReferenceMetadataPath=" + metadataPath; + if (!string.IsNullOrEmpty(framework)) + { + propertyArg += ";TargetFramework=" + framework; + } + if (!string.IsNullOrEmpty(configuration)) + { + propertyArg += ";Configuration=" + configuration; + } + if (!string.IsNullOrEmpty(runtime)) + { + propertyArg += ";RuntimeIdentifier=" + runtime; + } + + var args = new List + { + "msbuild", + "/target:WriteServiceProjectReferenceMetadata", + propertyArg, + "/verbosity:quiet", + "/nologo" + }; + + if (!string.IsNullOrEmpty(file)) + { + args.Add(file); + } + + var exitCode = Exe.Run("dotnet", args); + if (exitCode != 0) + { + throw new CommandException(Resources.GetMetadataFailed); + } + + metadata = File.ReadLines(metadataPath).Select(l => l.Split(new[] { ':' }, 2)) + .ToDictionary(s => s[0], s => s[1].TrimStart()); + } + finally + { + File.Delete(propsPath); + File.Delete(metadataPath); + File.Delete(targetsPath); + } + + var project = new Project + { + AssemblyName = metadata[nameof(AssemblyName)], + AssemblyPath = metadata[nameof(AssemblyPath)], + AssetsPath = metadata[nameof(AssetsPath)], + Configuration = metadata[nameof(Configuration)], + DefaultDocumentName = metadata[nameof(DefaultDocumentName)], + DefaultMethod = metadata[nameof(DefaultMethod)], + DefaultService = metadata[nameof(DefaultService)], + DefaultUri = metadata[nameof(DefaultUri)], + DepsPath = metadata[nameof(DepsPath)], + Directory = metadata[nameof(Directory)], + ExtensionsPath = metadata[nameof(ExtensionsPath)], + Name = metadata[nameof(Name)], + OutputPath = metadata[nameof(OutputPath)], + Platform = metadata[nameof(Platform)], + PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)], + RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)], + RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], + TargetFramework = metadata[nameof(TargetFramework)], + TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], + }; + + if (string.IsNullOrEmpty(project.AssemblyPath)) + { + throw new CommandException(Resources.GetMetadataValueFailed(nameof(AssemblyPath), "TargetPath")); + } + + if (string.IsNullOrEmpty(project.Directory)) + { + throw new CommandException(Resources.GetMetadataValueFailed(nameof(Directory), "ProjectDir")); + } + + if (string.IsNullOrEmpty(project.OutputPath)) + { + throw new CommandException(Resources.GetMetadataValueFailed(nameof(OutputPath), "OutDir")); + } + + if (!Path.IsPathRooted(project.Directory)) + { + project.Directory = Path.GetFullPath(Path.Combine(IODirectory.GetCurrentDirectory(), project.Directory)); + } + + if (!Path.IsPathRooted(project.AssemblyPath)) + { + project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath)); + } + + if (!Path.IsPathRooted(project.OutputPath)) + { + project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath)); + } + + // Some document generation tools support non-ASP.NET Core projects. + // Thus any of the remaining properties may be empty. + if (!(string.IsNullOrEmpty(project.AssetsPath) || Path.IsPathRooted(project.AssetsPath))) + { + project.AssetsPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssetsPath)); + } + + var configPath = $"{project.AssemblyPath}.config"; + if (File.Exists(configPath)) + { + project.ConfigPath = configPath; + } + + if (!(string.IsNullOrEmpty(project.DepsPath) || Path.IsPathRooted(project.DepsPath))) + { + project.DepsPath = Path.GetFullPath(Path.Combine(project.Directory, project.DepsPath)); + } + + if (!(string.IsNullOrEmpty(project.RuntimeConfigPath) || Path.IsPathRooted(project.RuntimeConfigPath))) + { + project.RuntimeConfigPath = Path.GetFullPath(Path.Combine(project.Directory, project.RuntimeConfigPath)); + } + + return project; + } + } +} diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/dotnet-getdocument/ProjectOptions.cs new file mode 100644 index 0000000000..bca160eeaa --- /dev/null +++ b/src/dotnet-getdocument/ProjectOptions.cs @@ -0,0 +1,30 @@ +// 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 GetDocument.Properties; +using Microsoft.DotNet.Cli.CommandLine; + +namespace GetDocument +{ + internal class ProjectOptions + { + public CommandOption Project { get; private set; } + + public CommandOption Framework { get; private set; } + + public CommandOption Configuration { get; private set; } + + public CommandOption Runtime { get; private set; } + + public CommandOption MSBuildProjectExtensionsPath { get; private set; } + + public void Configure(CommandLineApplication command) + { + Project = command.Option("-p|--project ", Resources.ProjectDescription); + Framework = command.Option("--framework ", Resources.FrameworkDescription); + Configuration = command.Option("--configuration ", Resources.ConfigurationDescription); + Runtime = command.Option("--runtime ", Resources.RuntimeDescription); + MSBuildProjectExtensionsPath = command.Option("--msbuildprojectextensionspath ", Resources.ProjectExtensionsDescription); + } + } +} diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..3ee7f0ba79 --- /dev/null +++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs @@ -0,0 +1,179 @@ +// + +using System; +using System.Reflection; +using System.Resources; +using JetBrains.Annotations; + +namespace GetDocument.Properties +{ + /// + /// This API supports the GetDocument infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// The configuration to use. + /// + public static string ConfigurationDescription + => GetString("ConfigurationDescription"); + + /// + /// dotnet getdocument + /// + public static string CommandFullName + => GetString("CommandFullName"); + + /// + /// The target framework. + /// + public static string FrameworkDescription + => GetString("FrameworkDescription"); + + /// + /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + /// + public static string GetMetadataFailed + => GetString("GetMetadataFailed"); + + /// + /// More than one project was found in the current working directory. Use the --project option. + /// + public static string MultipleProjects + => GetString("MultipleProjects"); + + /// + /// More than one project was found in directory '{projectDirectory}'. Specify one using its file name. + /// + public static string MultipleProjectsInDirectory([CanBeNull] object projectDirectory) + => string.Format( + GetString("MultipleProjectsInDirectory", nameof(projectDirectory)), + projectDirectory); + + /// + /// Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher. + /// + public static string NETCoreApp1Project([CanBeNull] object Project, [CanBeNull] object targetFrameworkVersion) + => string.Format( + GetString("NETCoreApp1Project", nameof(Project), nameof(targetFrameworkVersion)), + Project, targetFrameworkVersion); + + /// + /// Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// + public static string NETStandardProject([CanBeNull] object Project) + => string.Format( + GetString("NETStandardProject", nameof(Project)), + Project); + + /// + /// Do not colorize output. + /// + public static string NoColorDescription + => GetString("NoColorDescription"); + + /// + /// No project was found. Change the current working directory or use the --project option. + /// + public static string NoProject + => GetString("NoProject"); + + /// + /// No project was found in directory '{projectDirectory}'. + /// + public static string NoProjectInDirectory([CanBeNull] object projectDirectory) + => string.Format( + GetString("NoProjectInDirectory", nameof(projectDirectory)), + projectDirectory); + + /// + /// Prefix output with level. + /// + public static string PrefixDescription + => GetString("PrefixDescription"); + + /// + /// The project to use. + /// + public static string ProjectDescription + => GetString("ProjectDescription"); + + /// + /// The MSBuild project extensions path. Defaults to "obj". + /// + public static string ProjectExtensionsDescription + => GetString("ProjectExtensionsDescription"); + + /// + /// The runtime identifier to use. + /// + public static string RuntimeDescription + => GetString("RuntimeDescription"); + + /// + /// Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework. + /// + public static string UnsupportedFramework([CanBeNull] object Project, [CanBeNull] object targetFramework) + => string.Format( + GetString("UnsupportedFramework", nameof(Project), nameof(targetFramework)), + Project, targetFramework); + + /// + /// Using project '{project}'. + /// + public static string UsingProject([CanBeNull] object project) + => string.Format( + GetString("UsingProject", nameof(project)), + project); + + /// + /// Show verbose output. + /// + public static string VerboseDescription + => GetString("VerboseDescription"); + + /// + /// Writing '{file}'... + /// + public static string WritingFile([CanBeNull] object file) + => string.Format( + GetString("WritingFile", nameof(file)), + file); + + /// + /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// + public static string MustBuild + => GetString("MustBuild"); + + /// + /// The file to write the result to. + /// + public static string OutputDescription + => GetString("OutputDescription"); + + /// + /// Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set. + /// + public static string GetMetadataValueFailed([CanBeNull] object properrty, [CanBeNull] object msbuildProperty) + => string.Format( + GetString("GetMetadataValueFailed", nameof(properrty), nameof(msbuildProperty)), + properrty, msbuildProperty); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + + return value; + } + } +} + diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.tt b/src/dotnet-getdocument/Properties/Resources.Designer.tt new file mode 100644 index 0000000000..3f636a4db5 --- /dev/null +++ b/src/dotnet-getdocument/Properties/Resources.Designer.tt @@ -0,0 +1,6 @@ +<# + Session["ResourceFile"] = "Resources.resx"; + Session["AccessModifier"] = "internal"; + Session["NoDiagnostics"] = true; +#> +<#@ include file="..\..\tools\Resources.tt" #> diff --git a/src/dotnet-getdocument/Properties/Resources.resx b/src/dotnet-getdocument/Properties/Resources.resx new file mode 100644 index 0000000000..16e16b8086 --- /dev/null +++ b/src/dotnet-getdocument/Properties/Resources.resx @@ -0,0 +1,186 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + The configuration to use. + + + dotnet getdocument + + + The target framework. + + + Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + + + More than one project was found in the current working directory. Use the --project option. + + + More than one project was found in directory '{projectDirectory}'. Specify one using its file name. + + + Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher. + + + Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + + + Do not colorize output. + + + No project was found. Change the current working directory or use the --project option. + + + No project was found in directory '{projectDirectory}'. + + + Prefix output with level. + + + The project to use. + + + The MSBuild project extensions path. Defaults to "obj". + + + The runtime identifier to use. + + + Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework. + + + Using project '{project}'. + + + Show verbose output. + + + Writing '{file}'... + + + Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + + + The file to write the result to. + + + Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set. + + \ No newline at end of file diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props new file mode 100644 index 0000000000..30f045f3c0 --- /dev/null +++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props @@ -0,0 +1,17 @@ + + + + + + $(WriteServiceProjectReferenceMetadataDependsOn) + + + + + $(WriteServiceProjectReferenceMetadataDependsOn) + + + $(WriteServiceProjectReferenceMetadataDependsOn) + + + diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets new file mode 100644 index 0000000000..e8840ea37b --- /dev/null +++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj new file mode 100644 index 0000000000..b88db8367a --- /dev/null +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -0,0 +1,112 @@ + + + + + + + $(GenerateNuspecDependsOn);PopulateNuspec + + dotnet-getdocument + GetDocument Command-line Tool outside man + false + true + false + false + $(MSBuildProjectName).nuspec + Exe + true + GetDocument;command line;command-line;tool + GetDocument + netcoreapp2.1 + + + + + + + + + + + + + + + + + + + + + + + + + + TextTemplatingFileGenerator + Resources.Designer.cs + + + + + + + + + + True + True + Resources.Designer.tt + + + + + + + + + + + + + + + + <_Temporary Remove="@(Temporary)" /> + <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461" /> + <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461;Platform=x86" /> + <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=netcoreapp2.0" /> + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + authors=$(Authors); + copyright=$(Copyright); + description=$(Description); + iconUrl=$(PackageIconUrl); + id=$(PackageId); + InsiderNet461Output=..\GetDocumentInsider\bin\$(Configuration)\net461\publish\*; + InsiderNet461X86Output=..\GetDocumentInsider\bin\x86\$(Configuration)\net461\publish\*; + InsiderNetCoreOutput=..\GetDocumentInsider\bin\$(Configuration)\netcoreapp2.0\publish\*; + licenseUrl=$(PackageLicenseUrl); + Output=$(PublishDir)**\*; + OutputShims=$(IntermediateOutputPath)shims\**\*; + packageType=$(PackageType); + projectUrl=$(PackageProjectUrl); + repositoryCommit=$(RepositoryCommit); + repositoryUrl=$(RepositoryUrl); + SettingsFile=$(_ToolsSettingsFilePath); + tags=$(PackageTags.Replace(';', ' ')); + targetFramework=$(TargetFramework); + version=$(PackageVersion); + + + + diff --git a/src/dotnet-getdocument/dotnet-getdocument.nuspec b/src/dotnet-getdocument/dotnet-getdocument.nuspec new file mode 100644 index 0000000000..60c51f7869 --- /dev/null +++ b/src/dotnet-getdocument/dotnet-getdocument.nuspec @@ -0,0 +1,28 @@ + + + + $id$ + $version$ + $authors$ + true + $licenseUrl$ + $projectUrl$ + $iconUrl$ + $description$ + $copyright$ + $tags$ + + + + + + + + + + + + + + + From 25d0916b493d4698ee2d0ece5319c10cad537473 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 6 Sep 2018 09:25:54 -0700 Subject: [PATCH 279/316] Use one namespace for the three client code generation projects - also cleared out most uses of `GetDocument` and `GenerationTasks` in MSBuild and strings - temporarily fixed up T4 templates, adding Resources.tt (will remove custom generation soon) --- src/GetDocumentInsider/AnsiConsole.cs | 2 +- src/GetDocumentInsider/AnsiConstants.cs | 2 +- src/GetDocumentInsider/AnsiTextWriter.cs | 2 +- src/GetDocumentInsider/CommandException.cs | 2 +- .../Commands/CommandBase.cs | 4 +- .../Commands/GetDocumentCommand.cs | 6 +- .../Commands/GetDocumentCommandContext.cs | 2 +- .../Commands/GetDocumentCommandWorker.cs | 5 +- .../Commands/HelpCommandBase.cs | 2 +- .../Commands/ProjectCommandBase.cs | 4 +- .../GetDocumentInsider.csproj | 2 +- src/GetDocumentInsider/Json.cs | 4 +- src/GetDocumentInsider/LogWrapper.cs | 3 +- src/GetDocumentInsider/ProductInfo.cs | 12 +- src/GetDocumentInsider/Program.cs | 6 +- .../Properties/Resources.Designer.cs | 4 +- .../Properties/Resources.Designer.tt | 2 +- src/GetDocumentInsider/Reporter.cs | 4 +- src/GetDocumentInsider/WrappedException.cs | 2 +- .../DownloadFile.cs | 2 +- .../DownloadFileCore.cs | 2 +- .../GetFileReferenceMetadata.cs | 2 +- .../GetProjectReferenceMetadata.cs | 2 +- .../GetUriReferenceMetadata.cs | 2 +- .../ILogWrapper.cs | 2 +- .../LogWrapper.cs | 2 +- ...ft.Extensions.ApiDescription.Client.csproj | 1 - .../build/GenerationTasks.props | 16 +- .../build/GenerationTasks.targets | 10 +- .../Commands/InvokeCommand.cs | 4 +- src/dotnet-getdocument/Exe.cs | 2 +- src/dotnet-getdocument/Program.cs | 6 +- src/dotnet-getdocument/Project.cs | 6 +- src/dotnet-getdocument/ProjectOptions.cs | 4 +- .../Properties/Resources.Designer.cs | 12 +- .../Properties/Resources.Designer.tt | 2 +- .../Properties/Resources.resx | 8 +- .../dotnet-getdocument.csproj | 2 +- tools/Resources.tt | 226 ++++++++++++++++++ 39 files changed, 299 insertions(+), 84 deletions(-) create mode 100644 tools/Resources.tt diff --git a/src/GetDocumentInsider/AnsiConsole.cs b/src/GetDocumentInsider/AnsiConsole.cs index 30397229aa..c3f514bde4 100644 --- a/src/GetDocumentInsider/AnsiConsole.cs +++ b/src/GetDocumentInsider/AnsiConsole.cs @@ -3,7 +3,7 @@ using System; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class AnsiConsole { diff --git a/src/GetDocumentInsider/AnsiConstants.cs b/src/GetDocumentInsider/AnsiConstants.cs index e529180983..48f9ca2410 100644 --- a/src/GetDocumentInsider/AnsiConstants.cs +++ b/src/GetDocumentInsider/AnsiConstants.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. -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class AnsiConstants { diff --git a/src/GetDocumentInsider/AnsiTextWriter.cs b/src/GetDocumentInsider/AnsiTextWriter.cs index c8393d4810..471d9cf124 100644 --- a/src/GetDocumentInsider/AnsiTextWriter.cs +++ b/src/GetDocumentInsider/AnsiTextWriter.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class AnsiTextWriter { diff --git a/src/GetDocumentInsider/CommandException.cs b/src/GetDocumentInsider/CommandException.cs index 5d9778e61f..18680f9593 100644 --- a/src/GetDocumentInsider/CommandException.cs +++ b/src/GetDocumentInsider/CommandException.cs @@ -3,7 +3,7 @@ using System; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class CommandException : Exception { diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/GetDocumentInsider/Commands/CommandBase.cs index 4f66e51d5e..61e83151ce 100644 --- a/src/GetDocumentInsider/Commands/CommandBase.cs +++ b/src/GetDocumentInsider/Commands/CommandBase.cs @@ -1,10 +1,10 @@ // 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 GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal abstract class CommandBase { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs index f615e4399b..af61b02af6 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -8,17 +8,17 @@ using System.Reflection; #if NETCOREAPP2_0 using System.Runtime.Loader; #endif -using GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal class GetDocumentCommand : ProjectCommandBase { internal const string FallbackDocumentName = "v1"; internal const string FallbackMethod = "Generate"; internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; - private const string WorkerType = "GetDocument.Commands.GetDocumentCommandWorker"; + private const string WorkerType = "Microsoft.Extensions.ApiDescription.Client.Commands.GetDocumentCommandWorker"; private CommandOption _documentName; private CommandOption _method; diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs index 996e9e9701..65b3589af2 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -1,6 +1,6 @@ using System; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { [Serializable] public class GetDocumentCommandContext diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index c010165e61..97a1f4e809 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -4,14 +4,13 @@ using System.IO; using System.Reflection; using System.Threading; using System.Threading.Tasks; -using GenerationTasks; -using GetDocument.Properties; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.ApiDescription.Client.Properties; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal class GetDocumentCommandWorker { diff --git a/src/GetDocumentInsider/Commands/HelpCommandBase.cs b/src/GetDocumentInsider/Commands/HelpCommandBase.cs index 7e2c89cb5a..3f80564d54 100644 --- a/src/GetDocumentInsider/Commands/HelpCommandBase.cs +++ b/src/GetDocumentInsider/Commands/HelpCommandBase.cs @@ -3,7 +3,7 @@ using Microsoft.DotNet.Cli.CommandLine; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal class HelpCommandBase : CommandBase { diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs index a1a762f96c..71e74947d0 100644 --- a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs +++ b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs @@ -1,10 +1,10 @@ // 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 GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal abstract class ProjectCommandBase : HelpCommandBase { diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index 8fe28cd313..abd288edd7 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -4,7 +4,7 @@ GetDocument Command-line Tool inside man false Exe - GetDocument + Microsoft.Extensions.ApiDescription.Client netcoreapp2.0;net461 diff --git a/src/GetDocumentInsider/Json.cs b/src/GetDocumentInsider/Json.cs index acb62e449f..7e93ac21cd 100644 --- a/src/GetDocumentInsider/Json.cs +++ b/src/GetDocumentInsider/Json.cs @@ -1,10 +1,10 @@ // 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 GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class Json { diff --git a/src/GetDocumentInsider/LogWrapper.cs b/src/GetDocumentInsider/LogWrapper.cs index 0ab10cb744..b4ee5b4f9b 100644 --- a/src/GetDocumentInsider/LogWrapper.cs +++ b/src/GetDocumentInsider/LogWrapper.cs @@ -1,7 +1,6 @@ using System; -using GenerationTasks; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { public class LogWrapper : ILogWrapper { diff --git a/src/GetDocumentInsider/ProductInfo.cs b/src/GetDocumentInsider/ProductInfo.cs index 22045ee0df..8db001423a 100644 --- a/src/GetDocumentInsider/ProductInfo.cs +++ b/src/GetDocumentInsider/ProductInfo.cs @@ -3,18 +3,10 @@ using System.Reflection; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { - /// - /// This API supports the GetDocument infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static class ProductInfo + internal static class ProductInfo { - /// - /// This API supports the GetDocument infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// public static string GetVersion() => typeof(ProductInfo) .Assembly diff --git a/src/GetDocumentInsider/Program.cs b/src/GetDocumentInsider/Program.cs index 935fca4de0..2f2f43a4c0 100644 --- a/src/GetDocumentInsider/Program.cs +++ b/src/GetDocumentInsider/Program.cs @@ -3,10 +3,10 @@ using System; using System.Text; -using GetDocument.Commands; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Commands; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class Program { @@ -33,7 +33,7 @@ namespace GetDocument if (ex is CommandException || ex is CommandParsingException || (ex is WrappedException wrappedException - && wrappedException.Type == "GetDocument.Design.OperationException")) + && wrappedException.Type == "Microsoft.Extensions.ApiDescription.Client.Design.OperationException")) { Reporter.WriteVerbose(ex.ToString()); } diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs index 90463782ab..7f3066776c 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.cs +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Resources; using JetBrains.Annotations; -namespace GetDocument.Properties +namespace Microsoft.Extensions.ApiDescription.Client.Properties { /// /// This API supports the GetDocument infrastructure and is not intended to be used @@ -14,7 +14,7 @@ namespace GetDocument.Properties internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// The assembly to use. diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.tt b/src/GetDocumentInsider/Properties/Resources.Designer.tt index 3f636a4db5..29bb487272 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.tt +++ b/src/GetDocumentInsider/Properties/Resources.Designer.tt @@ -3,4 +3,4 @@ Session["AccessModifier"] = "internal"; Session["NoDiagnostics"] = true; #> -<#@ include file="..\..\tools\Resources.tt" #> +<#@ include file="..\..\..\tools\Resources.tt" #> diff --git a/src/GetDocumentInsider/Reporter.cs b/src/GetDocumentInsider/Reporter.cs index b7c0c264a5..abfec580fb 100644 --- a/src/GetDocumentInsider/Reporter.cs +++ b/src/GetDocumentInsider/Reporter.cs @@ -3,9 +3,9 @@ using System; using System.Linq; -using static GetDocument.AnsiConstants; +using static Microsoft.Extensions.ApiDescription.Client.AnsiConstants; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class Reporter { diff --git a/src/GetDocumentInsider/WrappedException.cs b/src/GetDocumentInsider/WrappedException.cs index 7cd7bfc0d3..ec90fb6061 100644 --- a/src/GetDocumentInsider/WrappedException.cs +++ b/src/GetDocumentInsider/WrappedException.cs @@ -3,7 +3,7 @@ using System; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class WrappedException : Exception { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs index a1341e0581..1e79fd9722 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs @@ -11,7 +11,7 @@ using Microsoft.Build.Utilities; using Task = System.Threading.Tasks.Task; using Utilities = Microsoft.Build.Utilities; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { /// /// Downloads a file. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs index bf8156e32f..5fd655fb85 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs @@ -6,7 +6,7 @@ using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { internal static class DownloadFileCore { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index 6ae50f8b21..2c0717b695 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -4,7 +4,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { /// /// Adds or corrects Namespace and OutputPath metadata in ServiceFileReference items. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs index e8ac95a819..379e674787 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -4,7 +4,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { /// /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs index 7d922e19dc..fc0f836ca0 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -4,7 +4,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { /// /// Adds or corrects DocumentPath metadata in ServiceUriReference items. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs index 7c773ab359..8f7f66396d 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs @@ -1,6 +1,6 @@ using System; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { internal interface ILogWrapper { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs index 75ae1407a8..98358ee91b 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs @@ -1,7 +1,7 @@ using System; using Microsoft.Build.Utilities; -namespace GenerationTasks +namespace Microsoft.Extensions.ApiDescription.Client { internal class LogWrapper : ILogWrapper { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj index 5dab6ea1aa..3041a7ea51 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj @@ -11,7 +11,6 @@ false $(MSBuildProjectName).nuspec Build Tasks;msbuild;DownloadFile;GetFilenameFromUri;code generation - GenerationTasks netstandard2.0;net461 diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props index dc67dca96a..989e7d9b23 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props @@ -2,15 +2,15 @@ - <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 - <_GenerationTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 - <_GenerationTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_GenerationTasksAssemblyTarget)/GenerationTasks.dll - <_GenerationTasksAssemblyTarget /> + <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 + <_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Client.dll + <_ApiDescriptionTasksAssemblyTarget /> - - - - + + + + true diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets index 7c70d7d279..1b9b1ac184 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets @@ -184,9 +184,9 @@ <_Temporary Remove="@(_Temporary)" /> - + - + @@ -196,7 +196,7 @@ - @@ -215,9 +215,9 @@ <_Temporary Remove="@(_Temporary)" /> - + - + diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs index cf3308ec46..f28aafacf4 100644 --- a/src/dotnet-getdocument/Commands/InvokeCommand.cs +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -6,12 +6,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; using System.Runtime.Versioning; -using GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace GetDocument.Commands +namespace Microsoft.Extensions.ApiDescription.Client.Commands { internal class InvokeCommand : HelpCommandBase { diff --git a/src/dotnet-getdocument/Exe.cs b/src/dotnet-getdocument/Exe.cs index 0c8f7d89ae..32595ce9ab 100644 --- a/src/dotnet-getdocument/Exe.cs +++ b/src/dotnet-getdocument/Exe.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class Exe { diff --git a/src/dotnet-getdocument/Program.cs b/src/dotnet-getdocument/Program.cs index 495573a776..b93d3e1144 100644 --- a/src/dotnet-getdocument/Program.cs +++ b/src/dotnet-getdocument/Program.cs @@ -2,11 +2,11 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using GetDocument.Commands; -using GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Commands; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal static class Program { diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs index 274960a08b..b778838ed8 100644 --- a/src/dotnet-getdocument/Project.cs +++ b/src/dotnet-getdocument/Project.cs @@ -5,14 +5,14 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using GetDocument.Properties; +using Microsoft.Extensions.ApiDescription.Client.Properties; using IODirectory = System.IO.Directory; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class Project { - private const string MSBuildResourceName = "GetDocument.ServiceProjectReferenceMetadata"; + private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client.ServiceProjectReferenceMetadata"; private Project() { diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/dotnet-getdocument/ProjectOptions.cs index bca160eeaa..59ddd4d48d 100644 --- a/src/dotnet-getdocument/ProjectOptions.cs +++ b/src/dotnet-getdocument/ProjectOptions.cs @@ -1,10 +1,10 @@ // 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 GetDocument.Properties; using Microsoft.DotNet.Cli.CommandLine; +using Microsoft.Extensions.ApiDescription.Client.Properties; -namespace GetDocument +namespace Microsoft.Extensions.ApiDescription.Client { internal class ProjectOptions { diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs index 3ee7f0ba79..351107083c 100644 --- a/src/dotnet-getdocument/Properties/Resources.Designer.cs +++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs @@ -5,7 +5,7 @@ using System.Reflection; using System.Resources; using JetBrains.Annotations; -namespace GetDocument.Properties +namespace Microsoft.Extensions.ApiDescription.Client.Properties { /// /// This API supports the GetDocument infrastructure and is not intended to be used @@ -14,7 +14,7 @@ namespace GetDocument.Properties internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("GetDocument.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// The configuration to use. @@ -23,7 +23,7 @@ namespace GetDocument.Properties => GetString("ConfigurationDescription"); /// - /// dotnet getdocument + /// dotnet-getdocument /// public static string CommandFullName => GetString("CommandFullName"); @@ -55,7 +55,7 @@ namespace GetDocument.Properties projectDirectory); /// - /// Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher. + /// Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. /// public static string NETCoreApp1Project([CanBeNull] object Project, [CanBeNull] object targetFrameworkVersion) => string.Format( @@ -63,7 +63,7 @@ namespace GetDocument.Properties Project, targetFrameworkVersion); /// - /// Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. /// public static string NETStandardProject([CanBeNull] object Project) => string.Format( @@ -115,7 +115,7 @@ namespace GetDocument.Properties => GetString("RuntimeDescription"); /// - /// Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework. + /// Project '{Project}' targets framework '{targetFramework}'. The dotnet-getdocument tool does not support this framework. /// public static string UnsupportedFramework([CanBeNull] object Project, [CanBeNull] object targetFramework) => string.Format( diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.tt b/src/dotnet-getdocument/Properties/Resources.Designer.tt index 3f636a4db5..29bb487272 100644 --- a/src/dotnet-getdocument/Properties/Resources.Designer.tt +++ b/src/dotnet-getdocument/Properties/Resources.Designer.tt @@ -3,4 +3,4 @@ Session["AccessModifier"] = "internal"; Session["NoDiagnostics"] = true; #> -<#@ include file="..\..\tools\Resources.tt" #> +<#@ include file="..\..\..\tools\Resources.tt" #> diff --git a/src/dotnet-getdocument/Properties/Resources.resx b/src/dotnet-getdocument/Properties/Resources.resx index 16e16b8086..dc049de189 100644 --- a/src/dotnet-getdocument/Properties/Resources.resx +++ b/src/dotnet-getdocument/Properties/Resources.resx @@ -121,7 +121,7 @@ The configuration to use. - dotnet getdocument + dotnet-getdocument The target framework. @@ -136,10 +136,10 @@ More than one project was found in directory '{projectDirectory}'. Specify one using its file name. - Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the GetDocument Command-line Tool only supports version 2.0 or higher. + Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. - Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the GetDocument Command-line Tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. Do not colorize output. @@ -163,7 +163,7 @@ The runtime identifier to use. - Project '{Project}' targets framework '{targetFramework}'. The GetDocument Command-line Tool does not support this framework. + Project '{Project}' targets framework '{targetFramework}'. The dotnet-getdocument tool does not support this framework. Using project '{project}'. diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index b88db8367a..73d57bffd9 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -16,7 +16,7 @@ Exe true GetDocument;command line;command-line;tool - GetDocument + Microsoft.Extensions.ApiDescription.Client netcoreapp2.1 diff --git a/tools/Resources.tt b/tools/Resources.tt new file mode 100644 index 0000000000..736a0cc442 --- /dev/null +++ b/tools/Resources.tt @@ -0,0 +1,226 @@ +<#@ template hostspecific="true" #> +<#@ assembly name="EnvDTE" #> +<#@ assembly name="System.Core" #> +<#@ assembly name="System.Windows.Forms" #> +<#@ import namespace="System.Collections" #> +<#@ import namespace="System.Collections.Generic" #> +<#@ import namespace="System.ComponentModel.Design" #> +<#@ import namespace="System.IO" #> +<#@ import namespace="System.Linq" #> +<#@ import namespace="System.Resources" #> +<#@ import namespace="System.Text.RegularExpressions" #> +<#@ import namespace="EnvDTE" #> +<# + var model = LoadResources(); +#> +// + +using System; +using System.Reflection; +using System.Resources; +using JetBrains.Annotations; +<# + if (!model.NoDiagnostics) + { +#> +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.Extensions.Logging; +<# + } +#> + +namespace <#= model.Namespace #> +{ + /// + /// This API supports the GetDocument infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + <#= model.AccessModifier #> static class <#= model.Class #> + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("<#= model.ResourceName #>", typeof(<#= model.Class #>).GetTypeInfo().Assembly); +<# + foreach (var resource in model.Resources) + { +#> + + /// +<# + foreach (var line in Lines(resource.Value)) + { +#> + /// <#= Xml(line) #> +<# + } +#> + /// +<# + if (resource.ForLogging) + { + if (resource.Types.Count() > 6) + { +#> + public static readonly FallbackEventDefinition <#= resource.Name #> + = new FallbackEventDefinition( + <#= resource.EventId #>, + LogLevel.<#= resource.Level #>, + "<#= resource.EventId #>", + _resourceManager.GetString("<#= resource.Name #>")); +<# + } + else + { + var genericTypes = resource.Types.Any() ? ("<" + List(resource.Types) + ">") : ""; +#> + public static readonly EventDefinition<#= genericTypes #> <#= resource.Name #> + = new EventDefinition<#= genericTypes #>( + <#= resource.EventId #>, + LogLevel.<#= resource.Level #>, + "<#= resource.EventId #>", + LoggerMessage.Define<#= genericTypes #>( + LogLevel.<#= resource.Level #>, + <#= resource.EventId #>, + _resourceManager.GetString("<#= resource.Name #>"))); +<# + } + } + else + { + if (resource.Parameters.Any()) + { +#> + public static string <#= resource.Name #>(<#= List("[CanBeNull] object ", resource.Parameters) #>) + => string.Format( + GetString("<#= resource.Name #>", <#= List("nameof(", resource.Parameters, ")") #>), + <#= List(resource.Parameters) #>); +<# + } + else + { +#> + public static string <#= resource.Name #> + => GetString("<#= resource.Name #>"); +<# + } + } + } +#> + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + + return value; + } + } +} +<#+ + ResourceFile LoadResources() + { + var result = new ResourceFile(); + + if (Session.ContainsKey("AccessModifier")) + { + result.AccessModifier = (string)Session["AccessModifier"]; + }; + + var services = (IServiceProvider)Host; + var dte = (DTE)services.GetService(typeof(DTE)); + + if (!Session.TryGetValue("NoDiagnostics", out var noDiagnostics)) + { + noDiagnostics = false; + } + + result.NoDiagnostics = (bool)noDiagnostics; + + var resourceFile = (string)Session["ResourceFile"]; + if (!Path.IsPathRooted(resourceFile)) + { + resourceFile = Host.ResolvePath(resourceFile); + } + + var resourceProjectItem = dte.Solution.FindProjectItem(resourceFile); + var templateProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile); + var project = templateProjectItem.ContainingProject; + var rootNamespace = (string)project.Properties.Item("RootNamespace").Value; + var resourceDir = Path.GetDirectoryName(resourceFile); + var projectDir = (string)project.Properties.Item("FullPath").Value; + var resourceNamespace = rootNamespace + "." + resourceDir.Substring(projectDir.Length) + .Replace(Path.DirectorySeparatorChar, '.'); + + result.Namespace = (string)resourceProjectItem.Properties.Item("CustomToolNamespace")?.Value; + if (string.IsNullOrEmpty(result.Namespace)) + { + result.Namespace = resourceNamespace; + } + + result.Class = Path.GetFileNameWithoutExtension(resourceFile); + + result.ResourceName = resourceNamespace + "." + result.Class; + + using (var reader = new ResXResourceReader(resourceFile)) + { + reader.UseResXDataNodes = true; + + result.Resources = Enumerable.ToList( + from DictionaryEntry r in reader + select new Resource((ResXDataNode)r.Value)); + } + + return result; + } + + IEnumerable Lines(string value) + => value.Split(new[] { Environment.NewLine }, StringSplitOptions.None); + + string Xml(string value) + => value.Replace("<", "<").Replace(">", ">"); + + string List(IEnumerable items) + => List(null, items); + + string List(string prefix, IEnumerable items, string suffix = null) + => string.Join(", ", items.Select(i => prefix + i + suffix)); + + class ResourceFile + { + public string Namespace { get; set; } + public string AccessModifier { get; set; } = "public"; + public string Class { get; set; } + public string ResourceName { get; set; } + public IEnumerable Resources { get; set; } + public bool NoDiagnostics { get; set; } + } + + class Resource + { + public Resource(ResXDataNode node) + { + Name = node.Name; + Value = (string)node.GetValue((ITypeResolutionService)null); + Parameters = Regex.Matches(Value, @"\{(\w+)\}") + .Cast() + .Select(m => m.Groups[1].Value) + .Distinct() + .ToList(); + + var eventInfo = node.Comment.Split(' '); + Level = eventInfo.FirstOrDefault() ?? "BadLevel"; + EventId = eventInfo.Skip(1).FirstOrDefault() ?? "BadEventId"; + Types = eventInfo.Skip(2).ToList(); + } + + public string Name { get; } + public string Value { get; } + public string EventId { get; } + public string Level { get; } + public bool ForLogging => Name.StartsWith("Log"); + public IEnumerable Parameters { get; } + public IEnumerable Types { get; } + } +#> \ No newline at end of file From e19c036f1140b913e60970e1d65488b1bd67faa5 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 6 Sep 2018 12:03:23 -0700 Subject: [PATCH 280/316] Fix up MSBuild files - correct filenames - remove NSwag-specific files --- ...ft.Extensions.ApiDescription.Client.props} | 1 - ....Extensions.ApiDescription.Client.targets} | 1 - .../build/NSwagServiceReference.props | 20 --- .../build/NSwagServiceReference.targets | 131 ------------------ 4 files changed, 153 deletions(-) rename src/Microsoft.Extensions.ApiDescription.Client/build/{GenerationTasks.props => Microsoft.Extensions.ApiDescription.Client.props} (99%) rename src/Microsoft.Extensions.ApiDescription.Client/build/{GenerationTasks.targets => Microsoft.Extensions.ApiDescription.Client.targets} (99%) delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props similarity index 99% rename from src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props rename to src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props index 989e7d9b23..45384b7d3e 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props @@ -1,6 +1,5 @@  - <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets similarity index 99% rename from src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets rename to src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index 1b9b1ac184..6a68aba48b 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/GenerationTasks.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -1,6 +1,5 @@  - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props deleted file mode 100644 index 4269fdd11c..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.props +++ /dev/null @@ -1,20 +0,0 @@ - - - - - _NSwagDocumentGenerator_GetMetadata; - _NSwagDocumentGenerator_Core; - _NSwagDocumentGenerator_SetMetadata - - - _NSwagCSharpCodeGenerator_GetMetadata; - _NSwagCSharpCodeGenerator_Core; - _NSwagCSharpCodeGenerator_SetMetadata - - - _NSwagTypeScriptCodeGenerator_GetMetadata; - _NSwagTypeScriptCodeGenerator_Core; - _NSwagTypeScriptCodeGenerator_SetMetadata - - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets deleted file mode 100644 index e73cd8d641..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/NSwagServiceReference.targets +++ /dev/null @@ -1,131 +0,0 @@ - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - <_NSwagTemporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'NSwag'))"> - $(NSwagDocumentGeneratorDefaultOptions) - - - - - - - - <_Command>$(NSwagExe_Core21) aspnetcore2swagger /Configuration:$(Configuration) /NoBuild:true - - - - - - - <_Command /> - - - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - - - - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagCSharp'))"> - $(NSwagCSharpCodeGeneratorDefaultOptions) - - <_NSwagTemporary Update="@(_NSwagTemporary)"> - $(ServiceFileReferenceDirectory)%(ClassName).cs - - - - - - - <_Command>$(NSwagExe_Core21) swagger2csclient /namespace:$(NSwagCSharpCodeGeneratorNamespace) - - - - - - - <_Command /> - - - - - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - - - - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - <_NSwagTemporary Include="@(ServiceFileReference -> WithMetadataValue('CodeGenerator', 'NSwagTypeScript'))"> - $(NSwagTypeScriptCodeGeneratorDefaultOptions) - - <_NSwagTemporary Update="@(_NSwagTemporary)"> - $(ServiceFileReferenceDirectory)%(ClassName).ts - - - - - - - <_Command>$(NSwagExe_Core21) swagger2tsclient /namespace:$(NSwagTypeScriptCodeGeneratorNamespace) - - - - - - - <_Command /> - - - - - - - - - <_NSwagTemporary Remove="@(_NSwagTemporary)" /> - - - - - From 6ffcf3571e41dfb699d47e73ee4172e47737b52f Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 6 Sep 2018 11:39:50 -0700 Subject: [PATCH 281/316] Remove T4 custom tool - use same generator as most other projects in aspnet repos - were not using named arguments to resource methods anyhow - update resources to use regular (numbered) format parameters - adjust to new `Resources` namespace; no need for separate `using` - use `Format...(...)` methods as necessary --- .../Commands/CommandBase.cs | 1 - .../Commands/GetDocumentCommand.cs | 13 +- .../Commands/GetDocumentCommandWorker.cs | 13 +- .../Commands/ProjectCommandBase.cs | 5 +- .../GetDocumentInsider.csproj | 19 - src/GetDocumentInsider/Json.cs | 1 - .../Properties/Resources.Designer.cs | 381 +++++++++++++----- .../Properties/Resources.Designer.tt | 6 - .../{Properties => }/Resources.resx | 34 +- .../Commands/InvokeCommand.cs | 13 +- src/dotnet-getdocument/Program.cs | 1 - src/dotnet-getdocument/Project.cs | 11 +- src/dotnet-getdocument/ProjectOptions.cs | 1 - .../Properties/Resources.Designer.cs | 325 +++++++++++---- .../Properties/Resources.Designer.tt | 6 - .../{Properties => }/Resources.resx | 72 ++-- .../dotnet-getdocument.csproj | 19 - tools/Resources.tt | 226 ----------- 18 files changed, 590 insertions(+), 557 deletions(-) delete mode 100644 src/GetDocumentInsider/Properties/Resources.Designer.tt rename src/GetDocumentInsider/{Properties => }/Resources.resx (89%) delete mode 100644 src/dotnet-getdocument/Properties/Resources.Designer.tt rename src/dotnet-getdocument/{Properties => }/Resources.resx (83%) delete mode 100644 tools/Resources.tt diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/GetDocumentInsider/Commands/CommandBase.cs index 61e83151ce..5ca14744cb 100644 --- a/src/GetDocumentInsider/Commands/CommandBase.cs +++ b/src/GetDocumentInsider/Commands/CommandBase.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client.Commands { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs index af61b02af6..583031b324 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -9,7 +9,6 @@ using System.Reflection; using System.Runtime.Loader; #endif using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client.Commands { @@ -32,10 +31,10 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands _documentName = command.Option( "--documentName ", - Resources.DocumentDescription(FallbackDocumentName)); - _method = command.Option("--method ", Resources.MethodDescription(FallbackMethod)); + Resources.FormatDocumentDescription(FallbackDocumentName)); + _method = command.Option("--method ", Resources.FormatMethodDescription(FallbackMethod)); _output = command.Option("--output ", Resources.OutputDescription); - _service = command.Option("--service ", Resources.ServiceDescription(FallbackService)); + _service = command.Option("--service ", Resources.FormatServiceDescription(FallbackService)); _uri = command.Option("--uri ", Resources.UriDescription); } @@ -45,17 +44,17 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (!_output.HasValue()) { - throw new CommandException(Resources.MissingOption(_output.LongName)); + throw new CommandException(Resources.FormatMissingOption(_output.LongName)); } if (_method.HasValue() && !_service.HasValue()) { - throw new CommandException(Resources.MissingOption(_service.LongName)); + throw new CommandException(Resources.FormatMissingOption(_service.LongName)); } if (_service.HasValue() && !_method.HasValue()) { - throw new CommandException(Resources.MissingOption(_method.LongName)); + throw new CommandException(Resources.FormatMissingOption(_method.LongName)); } } diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index 97a1f4e809..58d562ef2d 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -6,7 +6,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.TestHost; -using Microsoft.Extensions.ApiDescription.Client.Properties; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -21,7 +20,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands var entryPointType = assembly.EntryPoint?.DeclaringType; if (entryPointType == null) { - Reporter.WriteError(Resources.MissingEntryPoint(context.AssemblyPath)); + Reporter.WriteError(Resources.FormatMissingEntryPoint(context.AssemblyPath)); return 2; } @@ -68,9 +67,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands GetDocumentCommand.FallbackService : context.Service; - Reporter.WriteInformation(Resources.UsingDocument(documentName)); - Reporter.WriteInformation(Resources.UsingMethod(methodName)); - Reporter.WriteInformation(Resources.UsingService(serviceName)); + Reporter.WriteInformation(Resources.FormatUsingDocument(documentName)); + Reporter.WriteInformation(Resources.FormatUsingMethod(methodName)); + Reporter.WriteInformation(Resources.FormatUsingService(serviceName)); try { @@ -93,7 +92,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (!success) { - var message = Resources.MethodInvocationFailed(methodName, serviceName, documentName); + var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName); if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) { Reporter.WriteError(message); @@ -126,7 +125,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands { Debug.Assert(!string.IsNullOrEmpty(context.Uri)); - Reporter.WriteInformation(Resources.UsingUri(context.Uri)); + Reporter.WriteInformation(Resources.FormatUsingUri(context.Uri)); var httpClient = server.CreateClient(); await DownloadFileCore.DownloadAsync( diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs index 71e74947d0..7e6e49c750 100644 --- a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs +++ b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client.Commands { @@ -26,12 +25,12 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (!AssemblyPath.HasValue()) { - throw new CommandException(Resources.MissingOption(AssemblyPath.LongName)); + throw new CommandException(Resources.FormatMissingOption(AssemblyPath.LongName)); } if (!ToolsDirectory.HasValue()) { - throw new CommandException(Resources.MissingOption(ToolsDirectory.LongName)); + throw new CommandException(Resources.FormatMissingOption(ToolsDirectory.LongName)); } } } diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index abd288edd7..48de9b92d4 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -17,23 +17,4 @@ - - - - TextTemplatingFileGenerator - Resources.Designer.cs - - - - - - - - - - True - True - Resources.Designer.tt - - diff --git a/src/GetDocumentInsider/Json.cs b/src/GetDocumentInsider/Json.cs index 7e93ac21cd..53885c5825 100644 --- a/src/GetDocumentInsider/Json.cs +++ b/src/GetDocumentInsider/Json.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client { diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs index 7f3066776c..460ca76235 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.cs +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -1,207 +1,366 @@ // - -using System; -using System.Reflection; -using System.Resources; -using JetBrains.Annotations; - -namespace Microsoft.Extensions.ApiDescription.Client.Properties +namespace Microsoft.Extensions.ApiDescription.Client { - /// - /// This API supports the GetDocument infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// + using System.Globalization; + using System.Reflection; + using System.Resources; + internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); /// - /// The assembly to use. + /// The assembly to use. /// - public static string AssemblyDescription + internal static string AssemblyDescription + { + get => GetString("AssemblyDescription"); + } + + /// + /// The assembly to use. + /// + internal static string FormatAssemblyDescription() => GetString("AssemblyDescription"); /// - /// Show JSON output. + /// Show JSON output. /// - public static string JsonDescription + internal static string JsonDescription + { + get => GetString("JsonDescription"); + } + + /// + /// Show JSON output. + /// + internal static string FormatJsonDescription() => GetString("JsonDescription"); /// - /// Missing required option '--{option}'. + /// Missing required option '--{0}'. /// - public static string MissingOption([CanBeNull] object option) - => string.Format( - GetString("MissingOption", nameof(option)), - option); + internal static string MissingOption + { + get => GetString("MissingOption"); + } /// - /// Do not colorize output. + /// Missing required option '--{0}'. /// - public static string NoColorDescription + internal static string FormatMissingOption(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MissingOption"), p0); + + /// + /// Do not colorize output. + /// + internal static string NoColorDescription + { + get => GetString("NoColorDescription"); + } + + /// + /// Do not colorize output. + /// + internal static string FormatNoColorDescription() => GetString("NoColorDescription"); /// - /// The file to write the result to. + /// The file to write the result to. /// - public static string OutputDescription + internal static string OutputDescription + { + get => GetString("OutputDescription"); + } + + /// + /// The file to write the result to. + /// + internal static string FormatOutputDescription() => GetString("OutputDescription"); /// - /// Prefix console output with logging level. + /// Prefix console output with logging level. /// - public static string PrefixDescription + internal static string PrefixDescription + { + get => GetString("PrefixDescription"); + } + + /// + /// Prefix console output with logging level. + /// + internal static string FormatPrefixDescription() => GetString("PrefixDescription"); /// - /// Using application base '{appBase}'. + /// Using application base '{0}'. /// - public static string UsingApplicationBase([CanBeNull] object appBase) - => string.Format( - GetString("UsingApplicationBase", nameof(appBase)), - appBase); + internal static string UsingApplicationBase + { + get => GetString("UsingApplicationBase"); + } /// - /// Using assembly '{assembly}'. + /// Using application base '{0}'. /// - public static string UsingAssembly([CanBeNull] object assembly) - => string.Format( - GetString("UsingAssembly", nameof(assembly)), - assembly); + internal static string FormatUsingApplicationBase(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingApplicationBase"), p0); /// - /// Using configuration file '{config}'. + /// Using assembly '{0}'. /// - public static string UsingConfigurationFile([CanBeNull] object config) - => string.Format( - GetString("UsingConfigurationFile", nameof(config)), - config); + internal static string UsingAssembly + { + get => GetString("UsingAssembly"); + } /// - /// Show verbose output. + /// Using assembly '{0}'. /// - public static string VerboseDescription + internal static string FormatUsingAssembly(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingAssembly"), p0); + + /// + /// Using configuration file '{0}'. + /// + internal static string UsingConfigurationFile + { + get => GetString("UsingConfigurationFile"); + } + + /// + /// Using configuration file '{0}'. + /// + internal static string FormatUsingConfigurationFile(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingConfigurationFile"), p0); + + /// + /// Show verbose output. + /// + internal static string VerboseDescription + { + get => GetString("VerboseDescription"); + } + + /// + /// Show verbose output. + /// + internal static string FormatVerboseDescription() => GetString("VerboseDescription"); /// - /// Writing '{file}'... + /// Writing '{0}'... /// - public static string WritingFile([CanBeNull] object file) - => string.Format( - GetString("WritingFile", nameof(file)), - file); + internal static string WritingFile + { + get => GetString("WritingFile"); + } /// - /// Using working directory '{workingDirectory}'. + /// Writing '{0}'... /// - public static string UsingWorkingDirectory([CanBeNull] object workingDirectory) - => string.Format( - GetString("UsingWorkingDirectory", nameof(workingDirectory)), - workingDirectory); + internal static string FormatWritingFile(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); /// - /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// Using working directory '{0}'. /// - public static string ToolsDirectoryDescription + internal static string UsingWorkingDirectory + { + get => GetString("UsingWorkingDirectory"); + } + + /// + /// Using working directory '{0}'. + /// + internal static string FormatUsingWorkingDirectory(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingWorkingDirectory"), p0); + + /// + /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// + internal static string ToolsDirectoryDescription + { + get => GetString("ToolsDirectoryDescription"); + } + + /// + /// Location from which inside man was copied (in the .NET Framework case) or loaded. + /// + internal static string FormatToolsDirectoryDescription() => GetString("ToolsDirectoryDescription"); /// - /// The URI to download the document from. + /// The URI to download the document from. /// - public static string UriDescription + internal static string UriDescription + { + get => GetString("UriDescription"); + } + + /// + /// The URI to download the document from. + /// + internal static string FormatUriDescription() => GetString("UriDescription"); /// - /// The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'. + /// The name of the method to invoke on the '--service' instance. Default value '{0}'. /// - public static string MethodDescription([CanBeNull] object defaultMethod) - => string.Format( - GetString("MethodDescription", nameof(defaultMethod)), - defaultMethod); + internal static string MethodDescription + { + get => GetString("MethodDescription"); + } /// - /// The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'. + /// The name of the method to invoke on the '--service' instance. Default value '{0}'. /// - public static string ServiceDescription([CanBeNull] object defaultService) - => string.Format( - GetString("ServiceDescription", nameof(defaultService)), - defaultService); + internal static string FormatMethodDescription(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodDescription"), p0); /// - /// Missing required option '--{option1}' or '--{option2}'. + /// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. /// - public static string MissingOptions([CanBeNull] object option1, [CanBeNull] object option2) - => string.Format( - GetString("MissingOptions", nameof(option1), nameof(option2)), - option1, option2); + internal static string ServiceDescription + { + get => GetString("ServiceDescription"); + } /// - /// The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'. + /// The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. /// - public static string DocumentDescription([CanBeNull] object defaultDocumentName) - => string.Format( - GetString("DocumentDescription", nameof(defaultDocumentName)), - defaultDocumentName); + internal static string FormatServiceDescription(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0); /// - /// Using document name '{documentName}'. + /// Missing required option '--{0}' or '--{1}'. /// - public static string UsingDocument([CanBeNull] object documentName) - => string.Format( - GetString("UsingDocument", nameof(documentName)), - documentName); + internal static string MissingOptions + { + get => GetString("MissingOptions"); + } /// - /// Using method '{method}'. + /// Missing required option '--{0}' or '--{1}'. /// - public static string UsingMethod([CanBeNull] object method) - => string.Format( - GetString("UsingMethod", nameof(method)), - method); + internal static string FormatMissingOptions(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MissingOptions"), p0, p1); /// - /// Using service '{service}'. + /// The name of the document to pass to the '--method' method. Default value '{0}'. /// - public static string UsingService([CanBeNull] object service) - => string.Format( - GetString("UsingService", nameof(service)), - service); + internal static string DocumentDescription + { + get => GetString("DocumentDescription"); + } /// - /// Using URI '{uri}'. + /// The name of the document to pass to the '--method' method. Default value '{0}'. /// - public static string UsingUri([CanBeNull] object uri) - => string.Format( - GetString("UsingUri", nameof(uri)), - uri); + internal static string FormatDocumentDescription(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DocumentDescription"), p0); /// - /// Method '{method}' of service '{service}' failed to generate document '{documentName}'. + /// Using document name '{0}'. /// - public static string MethodInvocationFailed([CanBeNull] object method, [CanBeNull] object service, [CanBeNull] object documentName) - => string.Format( - GetString("MethodInvocationFailed", nameof(method), nameof(service), nameof(documentName)), - method, service, documentName); + internal static string UsingDocument + { + get => GetString("UsingDocument"); + } /// - /// Assembly '{assemblyPath}' does not contain an entry point. + /// Using document name '{0}'. /// - public static string MissingEntryPoint([CanBeNull] object assemblyPath) - => string.Format( - GetString("MissingEntryPoint", nameof(assemblyPath)), - assemblyPath); + internal static string FormatUsingDocument(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingDocument"), p0); + + /// + /// Using method '{0}'. + /// + internal static string UsingMethod + { + get => GetString("UsingMethod"); + } + + /// + /// Using method '{0}'. + /// + internal static string FormatUsingMethod(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingMethod"), p0); + + /// + /// Using service '{0}'. + /// + internal static string UsingService + { + get => GetString("UsingService"); + } + + /// + /// Using service '{0}'. + /// + internal static string FormatUsingService(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0); + + /// + /// Using URI '{0}'. + /// + internal static string UsingUri + { + get => GetString("UsingUri"); + } + + /// + /// Using URI '{0}'. + /// + internal static string FormatUsingUri(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingUri"), p0); + + /// + /// Method '{0}' of service '{1}' failed to generate document '{2}'. + /// + internal static string MethodInvocationFailed + { + get => GetString("MethodInvocationFailed"); + } + + /// + /// Method '{0}' of service '{1}' failed to generate document '{2}'. + /// + internal static string FormatMethodInvocationFailed(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodInvocationFailed"), p0, p1, p2); + + /// + /// Assembly '{0}' does not contain an entry point. + /// + internal static string MissingEntryPoint + { + get => GetString("MissingEntryPoint"); + } + + /// + /// Assembly '{0}' does not contain an entry point. + /// + internal static string FormatMissingEntryPoint(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0); private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); - for (var i = 0; i < formatterNames.Length; i++) + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) { - value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } } return value; } } } - diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.tt b/src/GetDocumentInsider/Properties/Resources.Designer.tt deleted file mode 100644 index 29bb487272..0000000000 --- a/src/GetDocumentInsider/Properties/Resources.Designer.tt +++ /dev/null @@ -1,6 +0,0 @@ -<# - Session["ResourceFile"] = "Resources.resx"; - Session["AccessModifier"] = "internal"; - Session["NoDiagnostics"] = true; -#> -<#@ include file="..\..\..\tools\Resources.tt" #> diff --git a/src/GetDocumentInsider/Properties/Resources.resx b/src/GetDocumentInsider/Resources.resx similarity index 89% rename from src/GetDocumentInsider/Properties/Resources.resx rename to src/GetDocumentInsider/Resources.resx index f5c08e3a40..f42b6144e6 100644 --- a/src/GetDocumentInsider/Properties/Resources.resx +++ b/src/GetDocumentInsider/Resources.resx @@ -124,7 +124,7 @@ Show JSON output. - Missing required option '--{option}'. + Missing required option '--{0}'. Do not colorize output. @@ -136,22 +136,22 @@ Prefix console output with logging level. - Using application base '{appBase}'. + Using application base '{0}'. - Using assembly '{assembly}'. + Using assembly '{0}'. - Using configuration file '{config}'. + Using configuration file '{0}'. Show verbose output. - Writing '{file}'... + Writing '{0}'... - Using working directory '{workingDirectory}'. + Using working directory '{0}'. Location from which inside man was copied (in the .NET Framework case) or loaded. @@ -160,33 +160,33 @@ The URI to download the document from. - The name of the method to invoke on the '--service' instance. Default value '{defaultMethod}'. + The name of the method to invoke on the '--service' instance. Default value '{0}'. - The qualified name of the service type to retrieve from dependency injection. Default value '{defaultService}'. + The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. - Missing required option '--{option1}' or '--{option2}'. + Missing required option '--{0}' or '--{1}'. - The name of the document to pass to the '--method' method. Default value '{defaultDocumentName}'. + The name of the document to pass to the '--method' method. Default value '{0}'. - Using document name '{documentName}'. + Using document name '{0}'. - Using method '{method}'. + Using method '{0}'. - Using service '{service}'. + Using service '{0}'. - Using URI '{uri}'. + Using URI '{0}'. - Method '{method}' of service '{service}' failed to generate document '{documentName}'. + Method '{0}' of service '{1}' failed to generate document '{2}'. - Assembly '{assemblyPath}' does not contain an entry point. + Assembly '{0}' does not contain an entry point. - \ No newline at end of file + diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs index f28aafacf4..b376b3b9d2 100644 --- a/src/dotnet-getdocument/Commands/InvokeCommand.cs +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Runtime.Versioning; using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; using Newtonsoft.Json; using Newtonsoft.Json.Linq; @@ -49,7 +48,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands _project.Value(), Resources.NoProject, Resources.MultipleProjects); - Reporter.WriteVerbose(Resources.UsingProject(projectFile)); + Reporter.WriteVerbose(Resources.FormatUsingProject(projectFile)); var project = Project.FromFile( projectFile, @@ -96,7 +95,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (targetFramework.Version < new Version(2, 0)) { throw new CommandException( - Resources.NETCoreApp1Project(project.Name, targetFramework.Version)); + Resources.FormatNETCoreApp1Project(project.Name, targetFramework.Version)); } args.Add("exec"); @@ -135,11 +134,11 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands break; case ".NETStandard": - throw new CommandException(Resources.NETStandardProject(project.Name)); + throw new CommandException(Resources.FormatNETStandardProject(project.Name)); default: throw new CommandException( - Resources.UnsupportedFramework(project.Name, targetFramework.Identifier)); + Resources.FormatUnsupportedFramework(project.Name, targetFramework.Identifier)); } args.AddRange(_args); @@ -224,14 +223,14 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands { throw new CommandException( specified - ? Resources.NoProjectInDirectory(path) + ? Resources.FormatNoProjectInDirectory(path) : errorWhenNoProject); } if (projectFiles.Count != 1) { throw new CommandException( specified - ? Resources.MultipleProjectsInDirectory(path) + ? Resources.FormatMultipleProjectsInDirectory(path) : errorWhenMultipleProjects); } diff --git a/src/dotnet-getdocument/Program.cs b/src/dotnet-getdocument/Program.cs index b93d3e1144..4e1f7fb5ca 100644 --- a/src/dotnet-getdocument/Program.cs +++ b/src/dotnet-getdocument/Program.cs @@ -4,7 +4,6 @@ using System; using Microsoft.DotNet.Cli.CommandLine; using Microsoft.Extensions.ApiDescription.Client.Commands; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client { diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs index b778838ed8..ce3325b7e8 100644 --- a/src/dotnet-getdocument/Project.cs +++ b/src/dotnet-getdocument/Project.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; -using Microsoft.Extensions.ApiDescription.Client.Properties; using IODirectory = System.IO.Directory; namespace Microsoft.Extensions.ApiDescription.Client @@ -82,7 +81,7 @@ namespace Microsoft.Extensions.ApiDescription.Client { using (var output = File.OpenWrite(propsPath)) { - Reporter.WriteVerbose(Resources.WritingFile(propsPath)); + Reporter.WriteVerbose(Resources.FormatWritingFile(propsPath)); input.CopyTo(output); } } @@ -93,7 +92,7 @@ namespace Microsoft.Extensions.ApiDescription.Client using (var output = File.OpenWrite(targetsPath)) { // NB: Copy always in case it changes - Reporter.WriteVerbose(Resources.WritingFile(targetsPath)); + Reporter.WriteVerbose(Resources.FormatWritingFile(targetsPath)); input.CopyTo(output); } } @@ -171,17 +170,17 @@ namespace Microsoft.Extensions.ApiDescription.Client if (string.IsNullOrEmpty(project.AssemblyPath)) { - throw new CommandException(Resources.GetMetadataValueFailed(nameof(AssemblyPath), "TargetPath")); + throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(AssemblyPath), "TargetPath")); } if (string.IsNullOrEmpty(project.Directory)) { - throw new CommandException(Resources.GetMetadataValueFailed(nameof(Directory), "ProjectDir")); + throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(Directory), "ProjectDir")); } if (string.IsNullOrEmpty(project.OutputPath)) { - throw new CommandException(Resources.GetMetadataValueFailed(nameof(OutputPath), "OutDir")); + throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(OutputPath), "OutDir")); } if (!Path.IsPathRooted(project.Directory)) diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/dotnet-getdocument/ProjectOptions.cs index 59ddd4d48d..f3b7d1148e 100644 --- a/src/dotnet-getdocument/ProjectOptions.cs +++ b/src/dotnet-getdocument/ProjectOptions.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Properties; namespace Microsoft.Extensions.ApiDescription.Client { diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs index 351107083c..eb38ce67a7 100644 --- a/src/dotnet-getdocument/Properties/Resources.Designer.cs +++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs @@ -1,179 +1,338 @@ // - -using System; -using System.Reflection; -using System.Resources; -using JetBrains.Annotations; - -namespace Microsoft.Extensions.ApiDescription.Client.Properties +namespace Microsoft.Extensions.ApiDescription.Client { - /// - /// This API supports the GetDocument infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// + using System.Globalization; + using System.Reflection; + using System.Resources; + internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Properties.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); /// - /// The configuration to use. + /// The configuration to use. /// - public static string ConfigurationDescription + internal static string ConfigurationDescription + { + get => GetString("ConfigurationDescription"); + } + + /// + /// The configuration to use. + /// + internal static string FormatConfigurationDescription() => GetString("ConfigurationDescription"); /// - /// dotnet-getdocument + /// dotnet-getdocument /// - public static string CommandFullName + internal static string CommandFullName + { + get => GetString("CommandFullName"); + } + + /// + /// dotnet-getdocument + /// + internal static string FormatCommandFullName() => GetString("CommandFullName"); /// - /// The target framework. + /// The target framework. /// - public static string FrameworkDescription + internal static string FrameworkDescription + { + get => GetString("FrameworkDescription"); + } + + /// + /// The target framework. + /// + internal static string FormatFrameworkDescription() => GetString("FrameworkDescription"); /// - /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. /// - public static string GetMetadataFailed + internal static string GetMetadataFailed + { + get => GetString("GetMetadataFailed"); + } + + /// + /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + /// + internal static string FormatGetMetadataFailed() => GetString("GetMetadataFailed"); /// - /// More than one project was found in the current working directory. Use the --project option. + /// More than one project was found in the current working directory. Use the --project option. /// - public static string MultipleProjects + internal static string MultipleProjects + { + get => GetString("MultipleProjects"); + } + + /// + /// More than one project was found in the current working directory. Use the --project option. + /// + internal static string FormatMultipleProjects() => GetString("MultipleProjects"); /// - /// More than one project was found in directory '{projectDirectory}'. Specify one using its file name. + /// More than one project was found in directory '{0}'. Specify one using its file name. /// - public static string MultipleProjectsInDirectory([CanBeNull] object projectDirectory) - => string.Format( - GetString("MultipleProjectsInDirectory", nameof(projectDirectory)), - projectDirectory); + internal static string MultipleProjectsInDirectory + { + get => GetString("MultipleProjectsInDirectory"); + } /// - /// Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. + /// More than one project was found in directory '{0}'. Specify one using its file name. /// - public static string NETCoreApp1Project([CanBeNull] object Project, [CanBeNull] object targetFrameworkVersion) - => string.Format( - GetString("NETCoreApp1Project", nameof(Project), nameof(targetFrameworkVersion)), - Project, targetFrameworkVersion); + internal static string FormatMultipleProjectsInDirectory(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("MultipleProjectsInDirectory"), p0); /// - /// Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. /// - public static string NETStandardProject([CanBeNull] object Project) - => string.Format( - GetString("NETStandardProject", nameof(Project)), - Project); + internal static string NETCoreApp1Project + { + get => GetString("NETCoreApp1Project"); + } /// - /// Do not colorize output. + /// Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. /// - public static string NoColorDescription + internal static string FormatNETCoreApp1Project(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("NETCoreApp1Project"), p0, p1); + + /// + /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// + internal static string NETStandardProject + { + get => GetString("NETStandardProject"); + } + + /// + /// Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + /// + internal static string FormatNETStandardProject(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("NETStandardProject"), p0); + + /// + /// Do not colorize output. + /// + internal static string NoColorDescription + { + get => GetString("NoColorDescription"); + } + + /// + /// Do not colorize output. + /// + internal static string FormatNoColorDescription() => GetString("NoColorDescription"); /// - /// No project was found. Change the current working directory or use the --project option. + /// No project was found. Change the current working directory or use the --project option. /// - public static string NoProject + internal static string NoProject + { + get => GetString("NoProject"); + } + + /// + /// No project was found. Change the current working directory or use the --project option. + /// + internal static string FormatNoProject() => GetString("NoProject"); /// - /// No project was found in directory '{projectDirectory}'. + /// No project was found in directory '{0}'. /// - public static string NoProjectInDirectory([CanBeNull] object projectDirectory) - => string.Format( - GetString("NoProjectInDirectory", nameof(projectDirectory)), - projectDirectory); + internal static string NoProjectInDirectory + { + get => GetString("NoProjectInDirectory"); + } /// - /// Prefix output with level. + /// No project was found in directory '{0}'. /// - public static string PrefixDescription + internal static string FormatNoProjectInDirectory(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("NoProjectInDirectory"), p0); + + /// + /// Prefix output with level. + /// + internal static string PrefixDescription + { + get => GetString("PrefixDescription"); + } + + /// + /// Prefix output with level. + /// + internal static string FormatPrefixDescription() => GetString("PrefixDescription"); /// - /// The project to use. + /// The project to use. /// - public static string ProjectDescription + internal static string ProjectDescription + { + get => GetString("ProjectDescription"); + } + + /// + /// The project to use. + /// + internal static string FormatProjectDescription() => GetString("ProjectDescription"); /// - /// The MSBuild project extensions path. Defaults to "obj". + /// The MSBuild project extensions path. Defaults to "obj". /// - public static string ProjectExtensionsDescription + internal static string ProjectExtensionsDescription + { + get => GetString("ProjectExtensionsDescription"); + } + + /// + /// The MSBuild project extensions path. Defaults to "obj". + /// + internal static string FormatProjectExtensionsDescription() => GetString("ProjectExtensionsDescription"); /// - /// The runtime identifier to use. + /// The runtime identifier to use. /// - public static string RuntimeDescription + internal static string RuntimeDescription + { + get => GetString("RuntimeDescription"); + } + + /// + /// The runtime identifier to use. + /// + internal static string FormatRuntimeDescription() => GetString("RuntimeDescription"); /// - /// Project '{Project}' targets framework '{targetFramework}'. The dotnet-getdocument tool does not support this framework. + /// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework. /// - public static string UnsupportedFramework([CanBeNull] object Project, [CanBeNull] object targetFramework) - => string.Format( - GetString("UnsupportedFramework", nameof(Project), nameof(targetFramework)), - Project, targetFramework); + internal static string UnsupportedFramework + { + get => GetString("UnsupportedFramework"); + } /// - /// Using project '{project}'. + /// Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework. /// - public static string UsingProject([CanBeNull] object project) - => string.Format( - GetString("UsingProject", nameof(project)), - project); + internal static string FormatUnsupportedFramework(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("UnsupportedFramework"), p0, p1); /// - /// Show verbose output. + /// Using project '{0}'. /// - public static string VerboseDescription + internal static string UsingProject + { + get => GetString("UsingProject"); + } + + /// + /// Using project '{0}'. + /// + internal static string FormatUsingProject(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("UsingProject"), p0); + + /// + /// Show verbose output. + /// + internal static string VerboseDescription + { + get => GetString("VerboseDescription"); + } + + /// + /// Show verbose output. + /// + internal static string FormatVerboseDescription() => GetString("VerboseDescription"); /// - /// Writing '{file}'... + /// Writing '{0}'... /// - public static string WritingFile([CanBeNull] object file) - => string.Format( - GetString("WritingFile", nameof(file)), - file); + internal static string WritingFile + { + get => GetString("WritingFile"); + } /// - /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// Writing '{0}'... /// - public static string MustBuild + internal static string FormatWritingFile(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); + + /// + /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// + internal static string MustBuild + { + get => GetString("MustBuild"); + } + + /// + /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// + internal static string FormatMustBuild() => GetString("MustBuild"); /// - /// The file to write the result to. + /// The file to write the result to. /// - public static string OutputDescription + internal static string OutputDescription + { + get => GetString("OutputDescription"); + } + + /// + /// The file to write the result to. + /// + internal static string FormatOutputDescription() => GetString("OutputDescription"); /// - /// Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set. + /// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set. /// - public static string GetMetadataValueFailed([CanBeNull] object properrty, [CanBeNull] object msbuildProperty) - => string.Format( - GetString("GetMetadataValueFailed", nameof(properrty), nameof(msbuildProperty)), - properrty, msbuildProperty); + internal static string GetMetadataValueFailed + { + get => GetString("GetMetadataValueFailed"); + } + + /// + /// Unable to retrieve '{0}' project metadata. Ensure '{1}' is set. + /// + internal static string FormatGetMetadataValueFailed(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("GetMetadataValueFailed"), p0, p1); private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); - for (var i = 0; i < formatterNames.Length; i++) + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) { - value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } } return value; } } } - diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.tt b/src/dotnet-getdocument/Properties/Resources.Designer.tt deleted file mode 100644 index 29bb487272..0000000000 --- a/src/dotnet-getdocument/Properties/Resources.Designer.tt +++ /dev/null @@ -1,6 +0,0 @@ -<# - Session["ResourceFile"] = "Resources.resx"; - Session["AccessModifier"] = "internal"; - Session["NoDiagnostics"] = true; -#> -<#@ include file="..\..\..\tools\Resources.tt" #> diff --git a/src/dotnet-getdocument/Properties/Resources.resx b/src/dotnet-getdocument/Resources.resx similarity index 83% rename from src/dotnet-getdocument/Properties/Resources.resx rename to src/dotnet-getdocument/Resources.resx index dc049de189..7db5873e7d 100644 --- a/src/dotnet-getdocument/Properties/Resources.resx +++ b/src/dotnet-getdocument/Resources.resx @@ -1,17 +1,17 @@  - @@ -133,13 +133,13 @@ More than one project was found in the current working directory. Use the --project option. - More than one project was found in directory '{projectDirectory}'. Specify one using its file name. + More than one project was found in directory '{0}'. Specify one using its file name. - Project '{Project}' targets framework '.NETCoreApp' version '{targetFrameworkVersion}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. + Project '{0}' targets framework '.NETCoreApp' version '{1}'. This version of the dotnet-getdocument tool only supports version 2.0 or higher. - Project '{Project}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. + Project '{0}' targets framework '.NETStandard'. There is no runtime associated with this framework, and projects targeting it cannot be executed directly. To use the dotnet-getdocument tool with this project, add an executable project targeting .NET Core or .NET Framework that references this project and specify it using the --project option; or, update this project to target .NET Core and / or .NET Framework. Do not colorize output. @@ -148,7 +148,7 @@ No project was found. Change the current working directory or use the --project option. - No project was found in directory '{projectDirectory}'. + No project was found in directory '{0}'. Prefix output with level. @@ -163,16 +163,16 @@ The runtime identifier to use. - Project '{Project}' targets framework '{targetFramework}'. The dotnet-getdocument tool does not support this framework. + Project '{0}' targets framework '{1}'. The dotnet-getdocument tool does not support this framework. - Using project '{project}'. + Using project '{0}'. Show verbose output. - Writing '{file}'... + Writing '{0}'... Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. @@ -181,6 +181,6 @@ The file to write the result to. - Unable to retrieve '{properrty}' project metadata. Ensure '{msbuildProperty}' is set. + Unable to retrieve '{0}' project metadata. Ensure '{1}' is set. - \ No newline at end of file + diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index 73d57bffd9..48569113b1 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -41,25 +41,6 @@ - - - TextTemplatingFileGenerator - Resources.Designer.cs - - - - - - - - - - True - True - Resources.Designer.tt - - - diff --git a/tools/Resources.tt b/tools/Resources.tt deleted file mode 100644 index 736a0cc442..0000000000 --- a/tools/Resources.tt +++ /dev/null @@ -1,226 +0,0 @@ -<#@ template hostspecific="true" #> -<#@ assembly name="EnvDTE" #> -<#@ assembly name="System.Core" #> -<#@ assembly name="System.Windows.Forms" #> -<#@ import namespace="System.Collections" #> -<#@ import namespace="System.Collections.Generic" #> -<#@ import namespace="System.ComponentModel.Design" #> -<#@ import namespace="System.IO" #> -<#@ import namespace="System.Linq" #> -<#@ import namespace="System.Resources" #> -<#@ import namespace="System.Text.RegularExpressions" #> -<#@ import namespace="EnvDTE" #> -<# - var model = LoadResources(); -#> -// - -using System; -using System.Reflection; -using System.Resources; -using JetBrains.Annotations; -<# - if (!model.NoDiagnostics) - { -#> -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.Extensions.Logging; -<# - } -#> - -namespace <#= model.Namespace #> -{ - /// - /// This API supports the GetDocument infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - <#= model.AccessModifier #> static class <#= model.Class #> - { - private static readonly ResourceManager _resourceManager - = new ResourceManager("<#= model.ResourceName #>", typeof(<#= model.Class #>).GetTypeInfo().Assembly); -<# - foreach (var resource in model.Resources) - { -#> - - /// -<# - foreach (var line in Lines(resource.Value)) - { -#> - /// <#= Xml(line) #> -<# - } -#> - /// -<# - if (resource.ForLogging) - { - if (resource.Types.Count() > 6) - { -#> - public static readonly FallbackEventDefinition <#= resource.Name #> - = new FallbackEventDefinition( - <#= resource.EventId #>, - LogLevel.<#= resource.Level #>, - "<#= resource.EventId #>", - _resourceManager.GetString("<#= resource.Name #>")); -<# - } - else - { - var genericTypes = resource.Types.Any() ? ("<" + List(resource.Types) + ">") : ""; -#> - public static readonly EventDefinition<#= genericTypes #> <#= resource.Name #> - = new EventDefinition<#= genericTypes #>( - <#= resource.EventId #>, - LogLevel.<#= resource.Level #>, - "<#= resource.EventId #>", - LoggerMessage.Define<#= genericTypes #>( - LogLevel.<#= resource.Level #>, - <#= resource.EventId #>, - _resourceManager.GetString("<#= resource.Name #>"))); -<# - } - } - else - { - if (resource.Parameters.Any()) - { -#> - public static string <#= resource.Name #>(<#= List("[CanBeNull] object ", resource.Parameters) #>) - => string.Format( - GetString("<#= resource.Name #>", <#= List("nameof(", resource.Parameters, ")") #>), - <#= List(resource.Parameters) #>); -<# - } - else - { -#> - public static string <#= resource.Name #> - => GetString("<#= resource.Name #>"); -<# - } - } - } -#> - - private static string GetString(string name, params string[] formatterNames) - { - var value = _resourceManager.GetString(name); - for (var i = 0; i < formatterNames.Length; i++) - { - value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); - } - - return value; - } - } -} -<#+ - ResourceFile LoadResources() - { - var result = new ResourceFile(); - - if (Session.ContainsKey("AccessModifier")) - { - result.AccessModifier = (string)Session["AccessModifier"]; - }; - - var services = (IServiceProvider)Host; - var dte = (DTE)services.GetService(typeof(DTE)); - - if (!Session.TryGetValue("NoDiagnostics", out var noDiagnostics)) - { - noDiagnostics = false; - } - - result.NoDiagnostics = (bool)noDiagnostics; - - var resourceFile = (string)Session["ResourceFile"]; - if (!Path.IsPathRooted(resourceFile)) - { - resourceFile = Host.ResolvePath(resourceFile); - } - - var resourceProjectItem = dte.Solution.FindProjectItem(resourceFile); - var templateProjectItem = dte.Solution.FindProjectItem(Host.TemplateFile); - var project = templateProjectItem.ContainingProject; - var rootNamespace = (string)project.Properties.Item("RootNamespace").Value; - var resourceDir = Path.GetDirectoryName(resourceFile); - var projectDir = (string)project.Properties.Item("FullPath").Value; - var resourceNamespace = rootNamespace + "." + resourceDir.Substring(projectDir.Length) - .Replace(Path.DirectorySeparatorChar, '.'); - - result.Namespace = (string)resourceProjectItem.Properties.Item("CustomToolNamespace")?.Value; - if (string.IsNullOrEmpty(result.Namespace)) - { - result.Namespace = resourceNamespace; - } - - result.Class = Path.GetFileNameWithoutExtension(resourceFile); - - result.ResourceName = resourceNamespace + "." + result.Class; - - using (var reader = new ResXResourceReader(resourceFile)) - { - reader.UseResXDataNodes = true; - - result.Resources = Enumerable.ToList( - from DictionaryEntry r in reader - select new Resource((ResXDataNode)r.Value)); - } - - return result; - } - - IEnumerable Lines(string value) - => value.Split(new[] { Environment.NewLine }, StringSplitOptions.None); - - string Xml(string value) - => value.Replace("<", "<").Replace(">", ">"); - - string List(IEnumerable items) - => List(null, items); - - string List(string prefix, IEnumerable items, string suffix = null) - => string.Join(", ", items.Select(i => prefix + i + suffix)); - - class ResourceFile - { - public string Namespace { get; set; } - public string AccessModifier { get; set; } = "public"; - public string Class { get; set; } - public string ResourceName { get; set; } - public IEnumerable Resources { get; set; } - public bool NoDiagnostics { get; set; } - } - - class Resource - { - public Resource(ResXDataNode node) - { - Name = node.Name; - Value = (string)node.GetValue((ITypeResolutionService)null); - Parameters = Regex.Matches(Value, @"\{(\w+)\}") - .Cast() - .Select(m => m.Groups[1].Value) - .Distinct() - .ToList(); - - var eventInfo = node.Comment.Split(' '); - Level = eventInfo.FirstOrDefault() ?? "BadLevel"; - EventId = eventInfo.Skip(1).FirstOrDefault() ?? "BadEventId"; - Types = eventInfo.Skip(2).ToList(); - } - - public string Name { get; } - public string Value { get; } - public string EventId { get; } - public string Level { get; } - public bool ForLogging => Name.StartsWith("Log"); - public IEnumerable Parameters { get; } - public IEnumerable Types { get; } - } -#> \ No newline at end of file From d0325ef26472be4e988d4b854a097762335e0979 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 9 Sep 2018 18:12:10 -0700 Subject: [PATCH 282/316] Remove `CodeAnnotations` - #8416 - turns out this required little on top of dougbu/remove.custom.tool --- src/GetDocumentInsider/CodeAnnotations.cs | 23 ------------------- .../dotnet-getdocument.csproj | 1 - 2 files changed, 24 deletions(-) delete mode 100644 src/GetDocumentInsider/CodeAnnotations.cs diff --git a/src/GetDocumentInsider/CodeAnnotations.cs b/src/GetDocumentInsider/CodeAnnotations.cs deleted file mode 100644 index 7a179f24d3..0000000000 --- a/src/GetDocumentInsider/CodeAnnotations.cs +++ /dev/null @@ -1,23 +0,0 @@ -// 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; - -namespace JetBrains.Annotations -{ - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | - AttributeTargets.Property | AttributeTargets.Delegate | - AttributeTargets.Field)] - internal sealed class NotNullAttribute : Attribute - { - } - - [AttributeUsage( - AttributeTargets.Method | AttributeTargets.Parameter | - AttributeTargets.Property | AttributeTargets.Delegate | - AttributeTargets.Field)] - internal sealed class CanBeNullAttribute : Attribute - { - } -} diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index 48569113b1..a5f550c9e8 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -24,7 +24,6 @@ - From a76ca293ef010f7a6cfe0578afcdf0d0da7db004 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sat, 8 Sep 2018 17:46:13 -0700 Subject: [PATCH 283/316] Add missing license headers to src files - #8415 - also correct a typo in Microsoft.AspNetCore.Mvc.Testing files' headers --- src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs | 5 ++++- src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs | 5 ++++- src/GetDocumentInsider/LogWrapper.cs | 5 ++++- src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs | 5 ++++- .../Handlers/CookieContainerHandler.cs | 2 +- .../Handlers/RedirectHandler.cs | 2 +- .../WebApplicationFactory.cs | 2 +- .../WebApplicationFactoryClientOptions.cs | 2 +- .../WebApplicationFactoryContentRootAttribute.cs | 2 +- .../DownloadFileCore.cs | 5 ++++- .../GetFileReferenceMetadata.cs | 5 ++++- .../GetProjectReferenceMetadata.cs | 5 ++++- .../GetUriReferenceMetadata.cs | 5 ++++- .../ILogWrapper.cs | 5 ++++- src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs | 5 ++++- 15 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs index 65b3589af2..a109977c20 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Extensions.ApiDescription.Client.Commands { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index 58d562ef2d..ac90640171 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Diagnostics; using System.IO; using System.Reflection; diff --git a/src/GetDocumentInsider/LogWrapper.cs b/src/GetDocumentInsider/LogWrapper.cs index b4ee5b4f9b..e7c4b7b8c7 100644 --- a/src/GetDocumentInsider/LogWrapper.cs +++ b/src/GetDocumentInsider/LogWrapper.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Extensions.ApiDescription.Client { diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs b/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs index 7485a15694..276bca6cbc 100644 --- a/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.AspNetCore.Mvc.Razor { diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs index bde4250b50..dedcf35f86 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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.Net; diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs index 8af03645ac..9addd609ec 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs index 85fcdadba2..8356180bd9 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs index c934b2fd56..cfc0244603 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs index a2ea31cb45..51832bd7a8 100644 --- a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs +++ b/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs index 5fd655fb85..7bae9e0227 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.IO; using System.Net.Http; using System.Net.Sockets; diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index 2c0717b695..68751b7aa5 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs index 379e674787..3fd84cf2e0 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs index fc0f836ca0..d63e2eb684 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs index 8f7f66396d..261e6df868 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs @@ -1,4 +1,7 @@ -using System; +// 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; namespace Microsoft.Extensions.ApiDescription.Client { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs index 98358ee91b..f058620621 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using Microsoft.Build.Utilities; namespace Microsoft.Extensions.ApiDescription.Client From 3f001750ad147dc3c69776cf5a6ff7aaad1dadd5 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sun, 9 Sep 2018 18:37:36 -0700 Subject: [PATCH 284/316] Rationalize code sharing between the three code generation projects - #8417 - just two files needed to be moved… --- .../DownloadFileCore.cs | 0 src/GetDocumentInsider/GetDocumentInsider.csproj | 2 -- .../ILogWrapper.cs | 0 .../Microsoft.Extensions.ApiDescription.Client.csproj | 2 ++ 4 files changed, 2 insertions(+), 2 deletions(-) rename src/{Microsoft.Extensions.ApiDescription.Client => GetDocumentInsider}/DownloadFileCore.cs (100%) rename src/{Microsoft.Extensions.ApiDescription.Client => GetDocumentInsider}/ILogWrapper.cs (100%) diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs b/src/GetDocumentInsider/DownloadFileCore.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Client/DownloadFileCore.cs rename to src/GetDocumentInsider/DownloadFileCore.cs diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index 48de9b92d4..3189f54d75 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -13,8 +13,6 @@ - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs b/src/GetDocumentInsider/ILogWrapper.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Client/ILogWrapper.cs rename to src/GetDocumentInsider/ILogWrapper.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj index 3041a7ea51..94e5128a4d 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj @@ -15,6 +15,8 @@ + + From 87e304334db5bfa8dcedc593c4ebf328fb23c735 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 6 Sep 2018 15:48:55 -0700 Subject: [PATCH 285/316] Remove batching requirements placed on code and document generator providers - #8419 - perform batching and `@(ServiceFileReference)` and `@(Compile)` additions in common code - take advantage of new simplicity in `DefaultDocumentGenerator` target - add metadata serialization / deserialization in support of passing items into `` - also ensure metadata values are escaped before calling `ITaskItem.SetMetadata(...)` - correct typos in Microsoft.Extensions.ApiDescription.Client.* e.g. in comments and metadata names - move last remaining `GenerationTasks` file nits: - combine `_ServiceProjectReferenceGenerator_Restore` and `_ServiceProjectReferenceGenerator_Build` targets - only build web sites projects once - remove unused `buildMultiTargeting` targets - remove qualification of metadata listed in an ``; will always exist - add / remove a few `Condition`s that were missing / redundant - move properties users won't normally set to Microsoft.Extensions.ApiDescription.Client.targets - shorten lines in MSBuild files --- .../GetCurrentItems.cs | 34 +++ .../GetFileReferenceMetadata.cs | 9 +- .../GetProjectReferenceMetadata.cs | 2 + .../GetUriReferenceMetadata.cs | 2 +- .../MetadataSerializer.cs | 147 ++++++++++++ ...oft.Extensions.ApiDescription.Client.props | 99 ++++---- ...t.Extensions.ApiDescription.Client.targets | 220 +++++++++++------- .../GenerationTasks.targets | 29 --- ...t.Extensions.ApiDescription.Client.targets | 9 + 9 files changed, 377 insertions(+), 174 deletions(-) create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs new file mode 100644 index 0000000000..975e716d64 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs @@ -0,0 +1,34 @@ +// 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.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Extensions.ApiDescription.Client +{ + /// + /// Restore s from given property value. + /// + public class GetCurrentItems : Task + { + /// + /// The property value to deserialize. + /// + [Required] + public string Input { get; set; } + + /// + /// The restored s. Will never contain more than one item. + /// + [Output] + public ITaskItem[] Outputs { get; set; } + + /// + public override bool Execute() + { + Outputs = new[] { MetadataSerializer.DeserializeMetadata(Input) }; + + return true; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index 68751b7aa5..f41d061974 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -61,18 +61,21 @@ namespace Microsoft.Extensions.ApiDescription.Client if (string.IsNullOrEmpty(@namespace)) { @namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace; - newItem.SetMetadata("Namespace", @namespace); + MetadataSerializer.SetMetadata(newItem, "Namespace", @namespace); } var outputPath = item.GetMetadata("OutputPath"); if (string.IsNullOrEmpty(outputPath)) { var className = item.GetMetadata("ClassName"); - outputPath = className + (isTypeScript ? ".ts" : ".cs"); + outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}"; } outputPath = GetFullPath(outputPath); - newItem.SetMetadata("OutputPath", outputPath); + MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath); + + // Add metadata which may be used as a property and passed to an inner build. + newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); } Outputs = outputs.ToArray(); diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs index 3fd84cf2e0..635863d417 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -52,6 +52,8 @@ namespace Microsoft.Extensions.ApiDescription.Client outputPath = className + (isTypeScript ? ".ts" : ".cs"); } + // Add metadata which may be used as a property and passed to an inner build. + newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); outputPath = GetFullPath(outputPath); newItem.SetMetadata("OutputPath", outputPath); } diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs index d63e2eb684..873ef57066 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -95,7 +95,7 @@ namespace Microsoft.Extensions.ApiDescription.Client } documentPath = GetFullPath(documentPath); - newItem.SetMetadata("DocumentPath", documentPath); + MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath); } Outputs = outputs.ToArray(); diff --git a/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs b/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs new file mode 100644 index 0000000000..3f430380a0 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs @@ -0,0 +1,147 @@ +// 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.Text; +using Microsoft.Build.Framework; +using Microsoft.Build.Utilities; + +namespace Microsoft.Extensions.ApiDescription.Client +{ + /// + /// Utility methods to serialize and deserialize metadata. + /// + /// + /// Based on and uses the same escaping as + /// https://github.com/Microsoft/msbuild/blob/e70a3159d64f9ed6ec3b60253ef863fa883a99b1/src/Shared/EscapingUtilities.cs + /// + public static class MetadataSerializer + { + private static readonly char[] CharsToEscape = { '%', '*', '?', '@', '$', '(', ')', ';', '\'' }; + private static readonly HashSet CharsToEscapeHash = new HashSet(CharsToEscape); + + /// + /// Add the given and to the . Or, + /// modify existing value to be . + /// + /// The to update. + /// The name of the new metadata. + /// The value of the new metadata. Assumed to be unescaped. + /// Uses same hex-encoded format as MSBuild's EscapeUtilities. + public static void SetMetadata(ITaskItem item, string key, string value) + { + if (item is ITaskItem2 item2) + { + item2.SetMetadataValueLiteral(key, value); + return; + } + + if (value.IndexOfAny(CharsToEscape) == -1) + { + item.SetMetadata(key, value); + return; + } + + var builder = new StringBuilder(); + EscapeValue(value, builder); + item.SetMetadata(key, builder.ToString()); + } + + /// + /// Serialize metadata for use as a property value passed into an inner build. + /// + /// The item to serialize. + /// A containing the serialized metadata. + /// Uses same hex-encoded format as MSBuild's EscapeUtilities. + public static string SerializeMetadata(ITaskItem item) + { + var builder = new StringBuilder(); + if (item is ITaskItem2 item2) + { + builder.Append($"Identity={item2.EvaluatedIncludeEscaped}"); + var metadata = item2.CloneCustomMetadataEscaped(); + foreach (var key in metadata.Keys) + { + var value = metadata[key]; + builder.Append($"|{key.ToString()}={value.ToString()}"); + } + } + else + { + builder.Append($"Identity="); + EscapeValue(item.ItemSpec, builder); + + var metadata = item.CloneCustomMetadata(); + foreach (var key in metadata.Keys) + { + builder.Append($"|{key.ToString()}="); + + var value = metadata[key]; + EscapeValue(value.ToString(), builder); + } + } + + return builder.ToString(); + } + + /// + /// Recreate an with metadata encoded in given . + /// + /// The serialized metadata. + /// The deserialized . + public static ITaskItem DeserializeMetadata(string value) + { + var metadata = value.Split('|'); + var item = new TaskItem(); + + // TaskItem implements ITaskITem2 explicitly and ITaskItem implicitly. + var item2 = (ITaskItem2)item; + foreach (var segment in metadata) + { + var keyAndValue = segment.Split(new[] { '=' }, count: 2); + if (string.Equals("Identity", keyAndValue[0])) + { + item2.EvaluatedIncludeEscaped = keyAndValue[1]; + continue; + } + + item2.SetMetadata(keyAndValue[0], keyAndValue[1]); + } + + return item; + } + + private static void EscapeValue(string value, StringBuilder builder) + { + if (string.IsNullOrEmpty(value)) + { + builder.Append(value); + return; + } + + if (value.IndexOfAny(CharsToEscape) == -1) + { + builder.Append(value); + return; + } + + foreach (var @char in value) + { + if (CharsToEscapeHash.Contains(@char)) + { + builder.Append('%'); + builder.Append(HexDigitChar(@char / 0x10)); + builder.Append(HexDigitChar(@char & 0x0F)); + continue; + } + + builder.Append(@char); + } + } + + private static char HexDigitChar(int x) + { + return (char)(x + (x < 10 ? '0' : ('a' - 10))); + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props index 45384b7d3e..2db402f022 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props @@ -6,49 +6,32 @@ <_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Client.dll <_ApiDescriptionTasksAssemblyTarget /> + + - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)')) + Condition="'$(ServiceProjectReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceProjectReferenceDirectory)')) - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)')) + Condition="'$(ServiceUriReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceUriReferenceDirectory)')) - true + true $([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) - $(RootNamespace) - $(RootNamespace) - - - _DefaultDocumentGenerator_GetMetadata; - _DefaultDocumentGenerator_Core; - _DefaultDocumentGenerator_SetMetadata - - - _ServiceProjectReferenceGenerator_GetTargetFramework; - _ServiceProjectReferenceGenerator_GetProjectTargetPath; - _ServiceProjectReferenceGenerator_Restore; - _ServiceProjectReferenceGenerator_Build; - _ServiceProjectReferenceGenerator_Core - - - _ServiceUriReferenceGenerator_GetMetadata; - _ServiceUriReferenceGenerator_Core - - - _CheckServiceReferences; - ServiceProjectReferenceGenerator; - ServiceUriReferenceGenerator; - _ServiceFileReferenceGenerator_GetMetadata; - _ServiceFileReferenceGenerator_Core - + Condition="'$(ServiceFileReferenceDirectory)' != ''">$([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) + $(RootNamespace) + $(RootNamespace) Default + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - %(Filename)Client + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index 6a68aba48b..bd25080826 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -1,11 +1,34 @@  + + + + _ServiceProjectReferenceGenerator_GetTargetFramework; + _ServiceProjectReferenceGenerator_GetProjectTargetPath; + _ServiceProjectReferenceGenerator_Build; + _ServiceProjectReferenceGenerator_Core; + _ServiceProjectReferenceGenerator_SetMetadata + + + _ServiceUriReferenceGenerator_GetMetadata; + _ServiceUriReferenceGenerator_Core + + + _CheckServiceReferences; + ServiceProjectReferenceGenerator; + ServiceUriReferenceGenerator; + _ServiceFileReferenceGenerator_GetMetadata; + _ServiceFileReferenceGenerator_Core; + _ServiceFileReferenceGenerator_SetMetadata + + + - - - @@ -38,7 +61,7 @@ - $(_TargetFramework) + $(_TargetFramework) <_Temporary Remove="@(_Temporary)" /> @@ -53,17 +76,19 @@ + Inputs="%(ServiceProjectReference.TargetFramework)%(FullPath)')" + Outputs="<not-a-file !>"> <_FullPath>%(ServiceProjectReference.FullPath) - <_TargetFramework>%(ServiceProjectReference.TargetFramework) + <_TargetFramework>%(ServiceProjectReference.ProjectTargetFramework) <_Temporary Remove="@(_Temporary)" /> - + + Condition="'%(ServiceProjectReference.FullPath)' == '$(_FullPath)' AND '%(ProjectTargetFramework)' == '$(_TargetFramework)'"> $(_ProjectTargetPath) <_Temporary Remove="@(_Temporary)" /> @@ -91,99 +116,85 @@ - - - - - + + RemoveProperties="TargetFramework;TargetFrameworks;RuntimeIdentifier" + Targets="Restore;Build" /> - + + + + + - + + + + + + + + + + + + + + + + + + + - + + + + - <_Temporary Remove="@(_Temporary)" /> - <_Temporary Include="@(ServiceProjectReference -> WithMetadataValue('DocumentGenerator', 'Default'))" /> + + + dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath) + $(DefaultDocumentGeneratorDefaultOptions) + + + %(Command) --framework %(ProjectTargetFramework) + %(Command) --configuration $(Configuration) + %(Command) --configuration %(ProjectConfiguration) + %(Command) --method %(Method) + %(Command) --service %(Service) + %(Command) --uri %(Uri) + %(Command) %(DefaultDocumentGeneratorOptions) + - - + + - - - - <_Command>dotnet getdocument --configuration $(Configuration) --no-build - - - <_Temporary Update="@(_Temporary)"> - $(DefaultDocumentGeneratorDefaultOptions) - $(_Command) --project %(FullPath) --output %(DocumentPath) --framework %(TargetFramework) - - <_Temporary Update="@(_Temporary)"> - %(Command) --uri %(_Temporary.Uri) - - <_Temporary Update="@(_Temporary)"> - %(Command) --service %(_Temporary.Service) --method %(_Temporary.Method) - - <_Temporary Update="@(_Temporary)"> - %(Command) %(_Temporary.Options) - - - - - - - - - - - - - - <_Temporary Remove="@(_Temporary)" /> - - - - - - + <_Temporary Remove="@(_Temporary)" /> - + @@ -199,9 +210,10 @@ DestinationPath="%(DocumentPath)" Overwrite="$(ServiceUriReferenceCheckIfNewer)" /> + - + @@ -209,12 +221,15 @@ - + <_Temporary Remove="@(_Temporary)" /> - + @@ -225,9 +240,38 @@ - + + + + + - + + + + + + + + + + + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets deleted file mode 100644 index 5e73fd66e1..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/GenerationTasks.targets +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets new file mode 100644 index 0000000000..a9c3d53836 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets @@ -0,0 +1,9 @@ + + + + + + From fb9393febfcff09185b7634ac88893ed6ed08d26 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 19 Sep 2018 10:10:19 -0700 Subject: [PATCH 286/316] Correct metadata additions and add errors about metadata - related to #8419 and (more generally) #7947 - add errors for missing required metadata - add errors for duplicate `%(DocumentPath)` and `%(OutputPath)` metadata - remove `[Required]` for task inputs that may be `null` or empty - correct `%(DocumentPath)`s generated in `GetProjectReferenceMetadata` task - use this task --- .../GetFileReferenceMetadata.cs | 49 ++++++- .../GetProjectReferenceMetadata.cs | 48 +++++-- .../GetUriReferenceMetadata.cs | 11 +- .../Properties/Resources.Designer.cs | 86 +++++++++++ .../Resources.resx | 134 ++++++++++++++++++ ...t.Extensions.ApiDescription.Client.targets | 33 +++-- 6 files changed, 331 insertions(+), 30 deletions(-) create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs create mode 100644 src/Microsoft.Extensions.ApiDescription.Client/Resources.resx diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index f41d061974..1241619418 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -10,7 +10,8 @@ using Microsoft.Build.Utilities; namespace Microsoft.Extensions.ApiDescription.Client { /// - /// Adds or corrects Namespace and OutputPath metadata in ServiceFileReference items. + /// Adds or corrects ClassName, Namespace and OutputPath metadata in ServiceFileReference items. Also stores final + /// metadata as SerializedMetadata. /// public class GetFileReferenceMetadata : Task { @@ -23,7 +24,6 @@ namespace Microsoft.Extensions.ApiDescription.Client /// /// Default directory for OutputPath values. /// - [Required] public string OutputDirectory { get; set; } /// @@ -49,14 +49,47 @@ namespace Microsoft.Extensions.ApiDescription.Client public override bool Execute() { var outputs = new List(Inputs.Length); + var destinations = new HashSet(); foreach (var item in Inputs) { var newItem = new TaskItem(item); outputs.Add(newItem); var codeGenerator = item.GetMetadata("CodeGenerator"); - var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); + if (string.IsNullOrEmpty("CodeGenerator")) + { + // This case occurs when user forgets to specify the required metadata. We have no default here. + string type; + if (!string.IsNullOrEmpty(item.GetMetadata("SourceProject"))) + { + type = "ServiceProjectReference"; + } + else if (!string.IsNullOrEmpty(item.GetMetadata("SourceUri"))) + { + type = "ServiceUriReference"; + } + else + { + type = "ServiceFileReference"; + } + Log.LogError(Resources.FormatInvalidEmptyMetadataValue("CodeGenerator", type, item.ItemSpec)); + } + + var className = item.GetMetadata("ClassName"); + if (string.IsNullOrEmpty(className)) + { + var filename = item.GetMetadata("Filename"); + className = $"{filename}Client"; + if (char.IsLower(className[0])) + { + className = char.ToUpper(className[0]) + className.Substring(startIndex: 1); + } + + MetadataSerializer.SetMetadata(newItem, "ClassName", className); + } + + var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); var @namespace = item.GetMetadata("Namespace"); if (string.IsNullOrEmpty(@namespace)) { @@ -67,20 +100,26 @@ namespace Microsoft.Extensions.ApiDescription.Client var outputPath = item.GetMetadata("OutputPath"); if (string.IsNullOrEmpty(outputPath)) { - var className = item.GetMetadata("ClassName"); outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}"; } outputPath = GetFullPath(outputPath); MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath); + if (!destinations.Add(outputPath)) + { + // This case may occur when user is experimenting e.g. with multiple code generators or options. + // May also occur when user accidentally duplicates OutputPath metadata. + Log.LogError(Resources.FormatDuplicateFileOutputPaths(outputPath)); + } + // Add metadata which may be used as a property and passed to an inner build. newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); } Outputs = outputs.ToArray(); - return true; + return !Log.HasLoggedErrors; } private string GetFullPath(string path) diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs index 635863d417..0c682107e5 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; using System.Collections.Generic; using System.IO; using Microsoft.Build.Framework; @@ -10,14 +9,14 @@ using Microsoft.Build.Utilities; namespace Microsoft.Extensions.ApiDescription.Client { /// - /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. + /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. Also stores final + /// metadata as SerializedMetadata. /// public class GetProjectReferenceMetadata : Task { /// /// Default directory for DocumentPath values. /// - [Required] public string DocumentDirectory { get; set; } /// @@ -37,30 +36,53 @@ namespace Microsoft.Extensions.ApiDescription.Client public override bool Execute() { var outputs = new List(Inputs.Length); + var destinations = new HashSet(); + foreach (var item in Inputs) { var newItem = new TaskItem(item); outputs.Add(newItem); - var codeGenerator = item.GetMetadata("CodeGenerator"); - var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); - - var outputPath = item.GetMetadata("OutputPath"); - if (string.IsNullOrEmpty(outputPath)) + var documentGenerator = item.GetMetadata("DocumentGenerator"); + if (string.IsNullOrEmpty(documentGenerator)) { - var className = item.GetMetadata("ClassName"); - outputPath = className + (isTypeScript ? ".ts" : ".cs"); + // This case occurs when user overrides the default metadata. + Log.LogError(Resources.FormatInvalidEmptyMetadataValue( + "DocumentGenerator", + "ServiceProjectReference", + item.ItemSpec)); + } + + var documentPath = item.GetMetadata("DocumentPath"); + if (string.IsNullOrEmpty(documentPath)) + { + var filename = item.GetMetadata("Filename"); + var documentName = item.GetMetadata("DocumentName"); + if (string.IsNullOrEmpty(documentName)) + { + documentName = "v1"; + } + + documentPath = $"{filename}.{documentName}.json"; + } + + documentPath = GetFullPath(documentPath); + MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath); + + if (!destinations.Add(documentPath)) + { + // This case may occur when user is experimenting e.g. with multiple generators or options. + // May also occur when user accidentally duplicates DocumentPath metadata. + Log.LogError(Resources.FormatDuplicateProjectDocumentPaths(documentPath)); } // Add metadata which may be used as a property and passed to an inner build. newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); - outputPath = GetFullPath(outputPath); - newItem.SetMetadata("OutputPath", outputPath); } Outputs = outputs.ToArray(); - return true; + return !Log.HasLoggedErrors; } private string GetFullPath(string path) diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs index 873ef57066..6870d2a337 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs @@ -17,7 +17,6 @@ namespace Microsoft.Extensions.ApiDescription.Client /// /// Default directory for DocumentPath metadata values. /// - [Required] public string DocumentDirectory { get; set; } /// @@ -36,6 +35,7 @@ namespace Microsoft.Extensions.ApiDescription.Client public override bool Execute() { var outputs = new List(Inputs.Length); + var destinations = new HashSet(); foreach (var item in Inputs) { var newItem = new TaskItem(item); @@ -96,11 +96,18 @@ namespace Microsoft.Extensions.ApiDescription.Client documentPath = GetFullPath(documentPath); MetadataSerializer.SetMetadata(newItem, "DocumentPath", documentPath); + + if (!destinations.Add(documentPath)) + { + // This case may occur when user is experimenting e.g. with multiple code generators or options. + // May also occur when user accidentally duplicates DocumentPath metadata. + Log.LogError(Resources.FormatDuplicateUriDocumentPaths(documentPath)); + } } Outputs = outputs.ToArray(); - return true; + return !Log.HasLoggedErrors; } private string GetFullPath(string path) diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs b/src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs new file mode 100644 index 0000000000..27049a4842 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs @@ -0,0 +1,86 @@ +// +namespace Microsoft.Extensions.ApiDescription.Client +{ + using System.Globalization; + using System.Reflection; + using System.Resources; + + internal static class Resources + { + private static readonly ResourceManager _resourceManager + = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); + + /// + /// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata. + /// + internal static string DuplicateFileOutputPaths + { + get => GetString("DuplicateFileOutputPaths"); + } + + /// + /// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata. + /// + internal static string FormatDuplicateFileOutputPaths(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateFileOutputPaths"), p0); + + /// + /// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata. + /// + internal static string DuplicateProjectDocumentPaths + { + get => GetString("DuplicateProjectDocumentPaths"); + } + + /// + /// Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata. + /// + internal static string FormatDuplicateProjectDocumentPaths(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateProjectDocumentPaths"), p0); + + /// + /// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata. + /// + internal static string DuplicateUriDocumentPaths + { + get => GetString("DuplicateUriDocumentPaths"); + } + + /// + /// Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata. + /// + internal static string FormatDuplicateUriDocumentPaths(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("DuplicateUriDocumentPaths"), p0); + + /// + /// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string. + /// + internal static string InvalidEmptyMetadataValue + { + get => GetString("InvalidEmptyMetadataValue"); + } + + /// + /// Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string. + /// + internal static string FormatInvalidEmptyMetadataValue(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidEmptyMetadataValue"), p0, p1, p2); + + private static string GetString(string name, params string[] formatterNames) + { + var value = _resourceManager.GetString(name); + + System.Diagnostics.Debug.Assert(value != null); + + if (formatterNames != null) + { + for (var i = 0; i < formatterNames.Length; i++) + { + value = value.Replace("{" + formatterNames[i] + "}", "{" + i + "}"); + } + } + + return value; + } + } +} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Resources.resx b/src/Microsoft.Extensions.ApiDescription.Client/Resources.resx new file mode 100644 index 0000000000..68fa954f09 --- /dev/null +++ b/src/Microsoft.Extensions.ApiDescription.Client/Resources.resx @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata. + ServiceProjectReference and ServiceUriReference items become ServiceFileReference items and all ServiceFileReference items must have unique OutputPath metadata. + + + Mutliple ServiceProjectReference items have DocumentPath='{0}'. ServiceProjectReference items must have unique DocumentPath metadata. + + + Mutliple ServiceUriReference items have DocumentPath='{0}'. ServiceUriReference items must have unique DocumentPath metadata. + Ignore corner case of ServiceProjectReference and ServiceUriReference items having the same DocumentPath. + + + Invalid {0} metadata value for {1} item '{2}'. {0} metadata must not be set to the empty string. + + \ No newline at end of file diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index bd25080826..ecbac4412e 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -5,6 +5,7 @@ _ServiceProjectReferenceGenerator_GetTargetFramework; _ServiceProjectReferenceGenerator_GetProjectTargetPath; + _ServiceProjectReferenceGenerator_GetMetadata; _ServiceProjectReferenceGenerator_Build; _ServiceProjectReferenceGenerator_Core; _ServiceProjectReferenceGenerator_SetMetadata @@ -14,7 +15,6 @@ _ServiceUriReferenceGenerator_Core - _CheckServiceReferences; ServiceProjectReferenceGenerator; ServiceUriReferenceGenerator; _ServiceFileReferenceGenerator_GetMetadata; @@ -23,15 +23,6 @@ - - - - - - @@ -116,6 +107,28 @@ + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + + + + + + + <_Temporary Remove="@(_Temporary)" /> + + + + + + Date: Tue, 25 Sep 2018 15:23:50 -0700 Subject: [PATCH 287/316] Fix a few issues with Microsoft.Extensions.ApiDescription.Client targets - follow-ups to 1646345955 and 9d109f5956 - fix `%(Command)` updates in `DefaultDocumentGenerator` target - later references to metadata values set within an item are not up-to-date - qualify values for `%(SourceProject)`, `%(SourceUri)` and `%(SourceDocument)` when setting that metadata - MSBuild can't distinguish unqualified metadata references unless using `` - fix `@(CurrentServiceFileReference)` items - was a copy 'n paste error in `_ServiceFileReferenceGenerator_Core` target - remove per-language default namespace values - do not add TypeScript files to `@(Compile)`; generally enhance final item additions - use `$(DefaultLanguageSourceExtension)` to help here - exclude generated source files with `%(OutputPath)` that does not match `$(DefaultLanguageSourceExtension)` - really support `%(OutputPath)` directories - stick with current `$(TargetFramework)` when building `...ReferenceGenerator_Inner` targets - `%(ProjectTargetFramework)` will not exist for all `@(ServiceFileReference)` items - building the current project, not a service project; `%(ProjectTargetFramework)` may not be supported nits: - shorten a few more long lines in Microsoft.Extensions.ApiDescription.Client.targets - reduce logging from that file - do not include `%(SerializedMetadata)` in `%(SerializedMetadata)` - caused extra-long serialization of items that were originally `@(ServiceProjectReference)`s - add more info to various comments - always use element syntax for metadata additions --- .../GetFileReferenceMetadata.cs | 54 +++---- ...oft.Extensions.ApiDescription.Client.props | 27 ++-- ...t.Extensions.ApiDescription.Client.targets | 133 ++++++++++++------ 3 files changed, 128 insertions(+), 86 deletions(-) diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs index 1241619418..54ae8b7500 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs @@ -15,23 +15,25 @@ namespace Microsoft.Extensions.ApiDescription.Client /// public class GetFileReferenceMetadata : Task { + private const string TypeScriptLanguageName = "TypeScript"; + /// - /// Default Namespace metadata value for C# output. + /// Extension to use in default OutputPath metadata value. Ignored when generating TypeScript. /// [Required] - public string CSharpNamespace { get; set; } + public string Extension { get; set; } + + /// + /// Default Namespace metadata value. + /// + [Required] + public string Namespace { get; set; } /// /// Default directory for OutputPath values. /// public string OutputDirectory { get; set; } - /// - /// Default Namespace metadata value for TypeScript output. - /// - [Required] - public string TypeScriptNamespace { get; set; } - /// /// The ServiceFileReference items to update. /// @@ -39,8 +41,7 @@ namespace Microsoft.Extensions.ApiDescription.Client public ITaskItem[] Inputs { get; set; } /// - /// The updated ServiceFileReference items. Will include Namespace and OutputPath metadata. OutputPath metadata - /// will contain full paths. + /// The updated ServiceFileReference items. Will include ClassName, Namespace and OutputPath metadata. /// [Output] public ITaskItem[] Outputs{ get; set; } @@ -50,6 +51,7 @@ namespace Microsoft.Extensions.ApiDescription.Client { var outputs = new List(Inputs.Length); var destinations = new HashSet(); + foreach (var item in Inputs) { var newItem = new TaskItem(item); @@ -89,22 +91,24 @@ namespace Microsoft.Extensions.ApiDescription.Client MetadataSerializer.SetMetadata(newItem, "ClassName", className); } - var isTypeScript = codeGenerator.EndsWith("TypeScript", StringComparison.OrdinalIgnoreCase); var @namespace = item.GetMetadata("Namespace"); if (string.IsNullOrEmpty(@namespace)) { - @namespace = isTypeScript ? CSharpNamespace : TypeScriptNamespace; - MetadataSerializer.SetMetadata(newItem, "Namespace", @namespace); + MetadataSerializer.SetMetadata(newItem, "Namespace", Namespace); } var outputPath = item.GetMetadata("OutputPath"); if (string.IsNullOrEmpty(outputPath)) { - outputPath = $"{className}{(isTypeScript ? ".ts" : ".cs")}"; + var isTypeScript = codeGenerator.EndsWith(TypeScriptLanguageName, StringComparison.OrdinalIgnoreCase); + outputPath = $"{className}{(isTypeScript ? ".ts" : Extension)}"; } - outputPath = GetFullPath(outputPath); - MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath); + // Place output file in correct directory (relative to project directory). + if (!Path.IsPathRooted(outputPath) && !string.IsNullOrEmpty(OutputDirectory)) + { + outputPath = Path.Combine(OutputDirectory, outputPath); + } if (!destinations.Add(outputPath)) { @@ -113,7 +117,10 @@ namespace Microsoft.Extensions.ApiDescription.Client Log.LogError(Resources.FormatDuplicateFileOutputPaths(outputPath)); } + MetadataSerializer.SetMetadata(newItem, "OutputPath", outputPath); + // Add metadata which may be used as a property and passed to an inner build. + newItem.RemoveMetadata("SerializedMetadata"); newItem.SetMetadata("SerializedMetadata", MetadataSerializer.SerializeMetadata(newItem)); } @@ -121,20 +128,5 @@ namespace Microsoft.Extensions.ApiDescription.Client return !Log.HasLoggedErrors; } - - private string GetFullPath(string path) - { - if (!Path.IsPathRooted(path)) - { - if (!string.IsNullOrEmpty(OutputDirectory)) - { - path = Path.Combine(OutputDirectory, path); - } - - path = Path.GetFullPath(path); - } - - return path; - } } } diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props index 2db402f022..d01b8044e8 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props @@ -12,7 +12,10 @@ - + true @@ -28,10 +31,8 @@ Condition="'$(ServiceFileReferenceCheckIfNewer)' == ''">true $([MSBuild]::EnsureTrailingSlash('$(ServiceFileReferenceDirectory)')) - $(RootNamespace) - $(RootNamespace) + + - + Default @@ -107,17 +111,20 @@ - + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index ecbac4412e..e7c60f9017 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -1,6 +1,6 @@  - + _ServiceProjectReferenceGenerator_GetTargetFramework; @@ -25,8 +25,10 @@ - - + @@ -45,14 +47,17 @@ - + <_TargetFrameworks>%(_Temporary.TargetFrameworks) <_TargetFramework>$(_TargetFrameworks.Split(';')[0]) - $(_TargetFramework) + $(_TargetFramework) <_Temporary Remove="@(_Temporary)" /> @@ -64,8 +69,11 @@ - - + @@ -77,9 +85,6 @@ <_Temporary Remove="@(_Temporary)" /> - + Condition="'%(FullPath)' == '$(_FullPath)' AND '%(ProjectTargetFramework)' == '$(_TargetFramework)'"> $(_ProjectTargetPath) <_Temporary Remove="@(_Temporary)" /> @@ -113,7 +118,8 @@ <_Temporary Remove="@(_Temporary)" /> - + @@ -124,9 +130,6 @@ <_Temporary Remove="@(_Temporary)" /> - - - - + - - - @@ -164,8 +165,9 @@ + Condition="Exists('%(ServiceProjectReference.DocumentPath)')"> + %(ServiceProjectReference.FullPath) + @@ -174,30 +176,36 @@ - - - dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath) $(DefaultDocumentGeneratorDefaultOptions) + $(Configuration) - + %(Command) --framework %(ProjectTargetFramework) - %(Command) --configuration $(Configuration) - %(Command) --configuration %(ProjectConfiguration) + + %(Command) --method %(Method) + + %(Command) --service %(Service) + + %(Command) --uri %(Uri) - %(Command) %(DefaultDocumentGeneratorOptions) + + + %(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions) - - + + + @@ -226,7 +234,9 @@ - + + %(ServiceUriReference.Identity) + @@ -240,9 +250,9 @@ + Extension="$(DefaultLanguageSourceExtension)" + Namespace="$(RootNamespace)" + OutputDirectory="$(ServiceFileReferenceDirectory)"> @@ -259,16 +269,14 @@ - + - - - @@ -279,8 +287,43 @@ unique. --> - - + <_Files Remove="@(_Files)" /> + <_Files Include="@(ServiceFileReference -> '%(OutputPath)')" + Condition="$([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))"> + $([System.IO.Path]::GetExtension('%(ServiceFileReference.OutputPath)')) + + <_Directories Remove="@(_Directories)" /> + <_Directories Include="@(ServiceFileReference -> '%(OutputPath)')" + Condition="Exists('%(ServiceFileReference.OutputPath)') AND ! $([System.IO.File]::Exists('%(ServiceFileReference.OutputPath)'))" /> + + + + + %(_Files.FullPath) + + + + + %(ServiceFileReference.FullPath) + + + + + + %(_Directories.FullPath) + + + + + %(_Directories.FullPath) + + + <_Files Remove="@(_Files)" /> + <_Directories Remove="@(_Directories)" /> From 5a58f81d8d9858d735aad21991413671cbf7dca4 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Wed, 3 Oct 2018 20:08:14 -0700 Subject: [PATCH 288/316] Use Internal.AspNetCore.Sdk as an MSBuild SDK in new projects - follow-up to 5bddd226a3 --- src/GetDocumentInsider/GetDocumentInsider.csproj | 2 +- .../Microsoft.Extensions.ApiDescription.Client.csproj | 2 +- src/dotnet-getdocument/dotnet-getdocument.csproj | 5 +---- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index 3189f54d75..93849f9cc4 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -1,4 +1,4 @@ - + GetDocument.Insider GetDocument Command-line Tool inside man diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj index 94e5128a4d..daec0b3b26 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj @@ -1,4 +1,4 @@ - + $(GenerateNuspecDependsOn);PopulateNuspec diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index a5f550c9e8..53967535a7 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -1,7 +1,4 @@ - - - - + $(GenerateNuspecDependsOn);PopulateNuspec From d3442f35904e8ae5c60fe03c6818f206bc32a6db Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Tue, 11 Sep 2018 16:20:07 -0700 Subject: [PATCH 289/316] Create a single Microsoft.Extensions.ApiDescription.Client package - #8428 - add signing-related and PackageVerifier configuration for new package - remove packaging configuration from dotnet-getdocument project - adjust `dotnet-getdocument` invocation to its new location - remove use of nonexistent (ignored) `dotnet-getdocument --no-build` option Remove `--uri` feature from `dotnet-getdocument` - reduce dependencies from Microsoft.AspNetCore.TestHost to Microsoft.AspNetCore.Hosting.Abstractions - assume web site depends on that - merge `DownloadFileCore` into `DownloadFile` - remove other unecessary code e.g. `WrappedException` was never `throw`n Correct issues in `DownloadFile` - e.g. dispose of `responseStream`, use `await` more, support FIPS-compliant machines nits: - clean up `Project` and the metadata it fetches - remove unnecessary `.props` and `.targets` files --- NuGetPackageVerifier.json | 19 ++- build/dependencies.props | 4 +- .../Commands/GetDocumentCommand.cs | 11 +- .../Commands/GetDocumentCommandContext.cs | 2 - .../Commands/GetDocumentCommandWorker.cs | 112 ++------------- src/GetDocumentInsider/DownloadFileCore.cs | 121 ---------------- .../GetDocumentInsider.csproj | 6 +- src/GetDocumentInsider/ILogWrapper.cs | 53 ------- src/GetDocumentInsider/Json.cs | 18 --- src/GetDocumentInsider/LogWrapper.cs | 31 ---- src/GetDocumentInsider/Program.cs | 5 +- .../Properties/Resources.Designer.cs | 126 ---------------- src/GetDocumentInsider/Resources.resx | 29 +--- src/GetDocumentInsider/WrappedException.cs | 24 ---- .../DownloadFile.cs | 134 ++++++++++++++++-- .../LogWrapper.cs | 38 ----- ...ft.Extensions.ApiDescription.Client.csproj | 47 ++++-- ...ft.Extensions.ApiDescription.Client.nuspec | 16 ++- .../ServiceProjectReferenceMetadata.targets | 32 ----- ...oft.Extensions.ApiDescription.Client.props | 5 - ...t.Extensions.ApiDescription.Client.targets | 5 +- .../Commands/InvokeCommand.cs | 6 - src/dotnet-getdocument/Project.cs | 81 +++++------ .../Properties/Resources.Designer.cs | 4 +- src/dotnet-getdocument/Resources.resx | 2 +- .../ServiceProjectReferenceMetadata.props | 17 --- .../ServiceProjectReferenceMetadata.targets | 26 ++-- .../dotnet-getdocument.csproj | 68 +-------- .../dotnet-getdocument.nuspec | 28 ---- 29 files changed, 261 insertions(+), 809 deletions(-) delete mode 100644 src/GetDocumentInsider/DownloadFileCore.cs delete mode 100644 src/GetDocumentInsider/ILogWrapper.cs delete mode 100644 src/GetDocumentInsider/Json.cs delete mode 100644 src/GetDocumentInsider/LogWrapper.cs delete mode 100644 src/GetDocumentInsider/WrappedException.cs delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs delete mode 100644 src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets delete mode 100644 src/dotnet-getdocument/ServiceProjectReferenceMetadata.props delete mode 100644 src/dotnet-getdocument/dotnet-getdocument.nuspec diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index b153ab1515..079b7cef51 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -1,7 +1,16 @@ { - "Default": { - "rules": [ - "DefaultCompositeRule" - ] + "Default": { + "rules": [ + "DefaultCompositeRule" + ], + "packages": { + "Microsoft.Extensions.ApiDescription.Client": { + "Exclusions": { + "BUILD_ITEMS_FRAMEWORK": { + "*": "Package includes tool with different target frameworks." + } + } + } } -} \ No newline at end of file + } +} diff --git a/build/dependencies.props b/build/dependencies.props index 304c16413f..eed739b78e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -32,6 +32,7 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 + 2.0.0 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 @@ -54,7 +55,6 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.0.0 2.2.0-preview3-35359 2.2.0-preview3-35359 5.2.6 diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs index 583031b324..cd45255f09 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -17,13 +17,11 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands internal const string FallbackDocumentName = "v1"; internal const string FallbackMethod = "Generate"; internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; - private const string WorkerType = "Microsoft.Extensions.ApiDescription.Client.Commands.GetDocumentCommandWorker"; private CommandOption _documentName; private CommandOption _method; private CommandOption _output; private CommandOption _service; - private CommandOption _uri; public override void Configure(CommandLineApplication command) { @@ -35,7 +33,6 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands _method = command.Option("--method ", Resources.FormatMethodDescription(FallbackMethod)); _output = command.Option("--output ", Resources.OutputDescription); _service = command.Option("--service ", Resources.FormatServiceDescription(FallbackService)); - _uri = command.Option("--uri ", Resources.UriDescription); } protected override void Validate() @@ -131,12 +128,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands #error target frameworks need to be updated. #endif - // Now safe to reference TestHost type. + // Now safe to reference the application's code. try { - var workerType = thisAssembly.GetType(WorkerType, throwOnError: true); - var methodInfo = workerType.GetMethod("Process", BindingFlags.Public | BindingFlags.Static); - var assemblyPath = AssemblyPath.Value(); var context = new GetDocumentCommandContext { @@ -147,10 +141,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands Method = _method.Value(), Output = _output.Value(), Service = _service.Value(), - Uri = _uri.Value(), }; - return (int)methodInfo.Invoke(obj: null, parameters: new[] { context }); + return GetDocumentCommandWorker.Process(context); } catch (Exception ex) { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs index a109977c20..c4fc0b6e45 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -21,7 +21,5 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands public string Output { get; set; } public string Service { get; set; } - - public string Uri { get; set; } } } diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index ac90640171..b0434e453c 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -2,15 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Diagnostics; using System.IO; using System.Reflection; -using System.Threading; -using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.AspNetCore.TestHost; using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Logging; namespace Microsoft.Extensions.ApiDescription.Client.Commands { @@ -34,25 +29,10 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands } var success = TryProcess(context, services); - if (!success && string.IsNullOrEmpty(context.Uri)) + if (!success) { - return 4; - } - - var builder = GetBuilder(entryPointType, context.AssemblyPath, context.AssemblyName); - if (builder == null) - { - return 5; - } - - // Mute the HttpsRedirectionMiddleware warning about HTTPS configuration. - builder.ConfigureLogging(loggingBuilder => loggingBuilder.AddFilter( - "Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware", - LogLevel.Error)); - - using (var server = new TestServer(builder)) - { - ProcessAsync(context, server).Wait(); + // As part of the aspnet/Mvc#8425 fix, return 4 here. + return 0; } return 0; @@ -95,15 +75,9 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (!success) { + // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists. var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName); - if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) - { - Reporter.WriteError(message); - } - else - { - Reporter.WriteWarning(message); - } + Reporter.WriteWarning(message); } return success; @@ -111,35 +85,14 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands catch (Exception ex) { var message = FormatException(ex); - if (string.IsNullOrEmpty(context.Uri) && !File.Exists(context.Output)) - { - Reporter.WriteError(message); - } - else - { - Reporter.WriteWarning(message); - } + + // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists. + Reporter.WriteWarning(message); return false; } } - public static async Task ProcessAsync(GetDocumentCommandContext context, TestServer server) - { - - Debug.Assert(!string.IsNullOrEmpty(context.Uri)); - Reporter.WriteInformation(Resources.FormatUsingUri(context.Uri)); - - var httpClient = server.CreateClient(); - await DownloadFileCore.DownloadAsync( - context.Uri, - context.Output, - httpClient, - new LogWrapper(), - CancellationToken.None, - timeoutSeconds: 60); - } - // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. private static IServiceProvider GetServices(Type entryPointType, string assemblyPath, string assemblyName) { @@ -203,54 +156,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands } } - // Startup - return new WebHostBuilder().UseStartup(assemblyName).Build().Services; - } - - // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. - private static IWebHostBuilder GetBuilder(Type entryPointType, string assemblyPath, string assemblyName) - { - var methodInfo = entryPointType.GetMethod("BuildWebHost"); - if (methodInfo != null) - { - // BuildWebHost cannot be used. Fall through, most likely to Startup fallback. - Reporter.WriteWarning( - "BuildWebHost method cannot be used. Falling back to minimal Startup configuration."); - } - - methodInfo = entryPointType.GetMethod("CreateWebHostBuilder"); - if (methodInfo != null) - { - // CreateWebHostBuilder - var parameters = methodInfo.GetParameters(); - if (!methodInfo.IsStatic || - parameters.Length != 1 || - typeof(string[]) != parameters[0].ParameterType || - typeof(IWebHostBuilder) != methodInfo.ReturnType) - { - Reporter.WriteError( - "CreateWebHostBuilder method found in {assemblyPath} does not have expected signature."); - - return null; - } - - try - { - var args = new[] { Array.Empty() }; - var builder = (IWebHostBuilder)methodInfo.Invoke(obj: null, parameters: args); - - return builder; - } - catch (Exception ex) - { - Reporter.WriteError($"CreateWebHostBuilder method threw: {FormatException(ex)}"); - - return null; - } - } - - // Startup - return new WebHostBuilder().UseStartup(assemblyName); + return null; } private static string FormatException(Exception exception) diff --git a/src/GetDocumentInsider/DownloadFileCore.cs b/src/GetDocumentInsider/DownloadFileCore.cs deleted file mode 100644 index 7bae9e0227..0000000000 --- a/src/GetDocumentInsider/DownloadFileCore.cs +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using System.Net.Http; -using System.Net.Sockets; -using System.Security.Cryptography; -using System.Threading; -using System.Threading.Tasks; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - internal static class DownloadFileCore - { - public static async Task DownloadAsync( - string uri, - string destinationPath, - HttpClient httpClient, - ILogWrapper log, - CancellationToken cancellationToken, - int timeoutSeconds) - { - // Timeout if the response has not begun within 1 minute - httpClient.Timeout = TimeSpan.FromMinutes(1); - - var destinationExists = File.Exists(destinationPath); - var reachedCopy = false; - try - { - using (var response = await httpClient.GetAsync(uri, cancellationToken)) - { - response.EnsureSuccessStatusCode(); - cancellationToken.ThrowIfCancellationRequested(); - - using (var responseStreamTask = response.Content.ReadAsStreamAsync()) - { - var finished = await Task.WhenAny( - responseStreamTask, - Task.Delay(TimeSpan.FromSeconds(timeoutSeconds))); - - if (!ReferenceEquals(responseStreamTask, finished)) - { - throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds."); - } - - var responseStream = await responseStreamTask; - if (destinationExists) - { - // Check hashes before using the downloaded information. - var downloadHash = GetHash(responseStream); - responseStream.Position = 0L; - - byte[] destinationHash; - using (var destinationStream = File.OpenRead(destinationPath)) - { - destinationHash = GetHash(destinationStream); - } - - var sameHashes = downloadHash.LongLength == destinationHash.LongLength; - for (var i = 0L; sameHashes && i < downloadHash.LongLength; i++) - { - sameHashes = downloadHash[i] == destinationHash[i]; - } - - if (sameHashes) - { - log.LogInformational($"Not overwriting existing and matching file '{destinationPath}'."); - return; - } - } - else - { - // May need to create directory to hold the file. - var destinationDirectory = Path.GetDirectoryName(destinationPath); - if (!(string.IsNullOrEmpty(destinationDirectory) || Directory.Exists(destinationDirectory))) - { - Directory.CreateDirectory(destinationDirectory); - } - } - - // Create or overwrite the destination file. - reachedCopy = true; - using (var outStream = File.Create(destinationPath)) - { - responseStream.CopyTo(outStream); - } - } - } - } - catch (HttpRequestException ex) when (destinationExists) - { - if (ex.InnerException is SocketException socketException) - { - log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'."); - } - else - { - log.LogWarning($"Unable to download {uri}: {ex.Message}"); - } - } - catch (Exception ex) - { - log.LogError($"Downloading '{uri}' failed."); - log.LogError(ex, showStackTrace: true); - if (reachedCopy) - { - File.Delete(destinationPath); - } - } - } - - private static byte[] GetHash(Stream stream) - { - using (var algorithm = SHA256.Create()) - { - return algorithm.ComputeHash(stream); - } - } - } -} diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index 93849f9cc4..b2c4fed5c6 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -13,6 +13,10 @@ - + + + + + diff --git a/src/GetDocumentInsider/ILogWrapper.cs b/src/GetDocumentInsider/ILogWrapper.cs deleted file mode 100644 index 261e6df868..0000000000 --- a/src/GetDocumentInsider/ILogWrapper.cs +++ /dev/null @@ -1,53 +0,0 @@ -// 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; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - internal interface ILogWrapper - { - /// - /// Logs specified informational . Implementations should be thread safe. - /// - /// The message to log. - /// Optional arguments for formatting the string. - /// - /// Thrown when is . - /// - void LogInformational(string message, params object[] messageArgs); - - /// - /// Logs a warning with the specified . Implementations should be thread safe. - /// - /// The message to log. - /// Optional arguments for formatting the string. - /// - /// Thrown when is . - /// - void LogWarning(string message, params object[] messageArgs); - - /// - /// Logs an error with the specified . Implementations should be thread safe. - /// - /// The message to log. - /// Optional arguments for formatting the string. - /// - /// Thrown when is . - /// - void LogError(string message, params object[] messageArgs); - - /// - /// Logs an error with the message and (optionally) the stack trace of the given . - /// Implementations should be thread safe. - /// - /// The to log. - /// - /// If , append stack trace to 's message. - /// - /// - /// Thrown when is . - /// - void LogError(Exception exception, bool showStackTrace); - } -} diff --git a/src/GetDocumentInsider/Json.cs b/src/GetDocumentInsider/Json.cs deleted file mode 100644 index 53885c5825..0000000000 --- a/src/GetDocumentInsider/Json.cs +++ /dev/null @@ -1,18 +0,0 @@ -// 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.DotNet.Cli.CommandLine; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - internal static class Json - { - public static CommandOption ConfigureOption(CommandLineApplication command) - => command.Option("--json", Resources.JsonDescription); - - public static string Literal(string text) - => text != null - ? "\"" + text.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"" - : "null"; - } -} diff --git a/src/GetDocumentInsider/LogWrapper.cs b/src/GetDocumentInsider/LogWrapper.cs deleted file mode 100644 index e7c4b7b8c7..0000000000 --- a/src/GetDocumentInsider/LogWrapper.cs +++ /dev/null @@ -1,31 +0,0 @@ -// 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; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - public class LogWrapper : ILogWrapper - { - public void LogError(string message, params object[] messageArgs) - { - Reporter.WriteError(string.Format(message, messageArgs)); - } - - public void LogError(Exception exception, bool showStackTrace) - { - var message = showStackTrace ? exception.ToString() : exception.Message; - Reporter.WriteError(message); - } - - public void LogInformational(string message, params object[] messageArgs) - { - Reporter.WriteInformation(string.Format(message, messageArgs)); - } - - public void LogWarning(string message, params object[] messageArgs) - { - Reporter.WriteWarning(string.Format(message, messageArgs)); - } - } -} diff --git a/src/GetDocumentInsider/Program.cs b/src/GetDocumentInsider/Program.cs index 2f2f43a4c0..af1bdc298a 100644 --- a/src/GetDocumentInsider/Program.cs +++ b/src/GetDocumentInsider/Program.cs @@ -30,10 +30,7 @@ namespace Microsoft.Extensions.ApiDescription.Client } catch (Exception ex) { - if (ex is CommandException - || ex is CommandParsingException - || (ex is WrappedException wrappedException - && wrappedException.Type == "Microsoft.Extensions.ApiDescription.Client.Design.OperationException")) + if (ex is CommandException || ex is CommandParsingException) { Reporter.WriteVerbose(ex.ToString()); } diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs index 460ca76235..c9576ef8c0 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.cs +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -24,20 +24,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatAssemblyDescription() => GetString("AssemblyDescription"); - /// - /// Show JSON output. - /// - internal static string JsonDescription - { - get => GetString("JsonDescription"); - } - - /// - /// Show JSON output. - /// - internal static string FormatJsonDescription() - => GetString("JsonDescription"); - /// /// Missing required option '--{0}'. /// @@ -94,48 +80,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatPrefixDescription() => GetString("PrefixDescription"); - /// - /// Using application base '{0}'. - /// - internal static string UsingApplicationBase - { - get => GetString("UsingApplicationBase"); - } - - /// - /// Using application base '{0}'. - /// - internal static string FormatUsingApplicationBase(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingApplicationBase"), p0); - - /// - /// Using assembly '{0}'. - /// - internal static string UsingAssembly - { - get => GetString("UsingAssembly"); - } - - /// - /// Using assembly '{0}'. - /// - internal static string FormatUsingAssembly(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingAssembly"), p0); - - /// - /// Using configuration file '{0}'. - /// - internal static string UsingConfigurationFile - { - get => GetString("UsingConfigurationFile"); - } - - /// - /// Using configuration file '{0}'. - /// - internal static string FormatUsingConfigurationFile(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingConfigurationFile"), p0); - /// /// Show verbose output. /// @@ -150,34 +94,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatVerboseDescription() => GetString("VerboseDescription"); - /// - /// Writing '{0}'... - /// - internal static string WritingFile - { - get => GetString("WritingFile"); - } - - /// - /// Writing '{0}'... - /// - internal static string FormatWritingFile(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); - - /// - /// Using working directory '{0}'. - /// - internal static string UsingWorkingDirectory - { - get => GetString("UsingWorkingDirectory"); - } - - /// - /// Using working directory '{0}'. - /// - internal static string FormatUsingWorkingDirectory(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingWorkingDirectory"), p0); - /// /// Location from which inside man was copied (in the .NET Framework case) or loaded. /// @@ -192,20 +108,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatToolsDirectoryDescription() => GetString("ToolsDirectoryDescription"); - /// - /// The URI to download the document from. - /// - internal static string UriDescription - { - get => GetString("UriDescription"); - } - - /// - /// The URI to download the document from. - /// - internal static string FormatUriDescription() - => GetString("UriDescription"); - /// /// The name of the method to invoke on the '--service' instance. Default value '{0}'. /// @@ -234,20 +136,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatServiceDescription(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("ServiceDescription"), p0); - /// - /// Missing required option '--{0}' or '--{1}'. - /// - internal static string MissingOptions - { - get => GetString("MissingOptions"); - } - - /// - /// Missing required option '--{0}' or '--{1}'. - /// - internal static string FormatMissingOptions(object p0, object p1) - => string.Format(CultureInfo.CurrentCulture, GetString("MissingOptions"), p0, p1); - /// /// The name of the document to pass to the '--method' method. Default value '{0}'. /// @@ -304,20 +192,6 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static string FormatUsingService(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("UsingService"), p0); - /// - /// Using URI '{0}'. - /// - internal static string UsingUri - { - get => GetString("UsingUri"); - } - - /// - /// Using URI '{0}'. - /// - internal static string FormatUsingUri(object p0) - => string.Format(CultureInfo.CurrentCulture, GetString("UsingUri"), p0); - /// /// Method '{0}' of service '{1}' failed to generate document '{2}'. /// diff --git a/src/GetDocumentInsider/Resources.resx b/src/GetDocumentInsider/Resources.resx index f42b6144e6..fffabb44f3 100644 --- a/src/GetDocumentInsider/Resources.resx +++ b/src/GetDocumentInsider/Resources.resx @@ -120,9 +120,6 @@ The assembly to use. - - Show JSON output. - Missing required option '--{0}'. @@ -135,39 +132,18 @@ Prefix console output with logging level. - - Using application base '{0}'. - - - Using assembly '{0}'. - - - Using configuration file '{0}'. - Show verbose output. - - Writing '{0}'... - - - Using working directory '{0}'. - Location from which inside man was copied (in the .NET Framework case) or loaded. - - The URI to download the document from. - The name of the method to invoke on the '--service' instance. Default value '{0}'. The qualified name of the service type to retrieve from dependency injection. Default value '{0}'. - - Missing required option '--{0}' or '--{1}'. - The name of the document to pass to the '--method' method. Default value '{0}'. @@ -180,13 +156,10 @@ Using service '{0}'. - - Using URI '{0}'. - Method '{0}' of service '{1}' failed to generate document '{2}'. Assembly '{0}' does not contain an entry point. - + \ No newline at end of file diff --git a/src/GetDocumentInsider/WrappedException.cs b/src/GetDocumentInsider/WrappedException.cs deleted file mode 100644 index ec90fb6061..0000000000 --- a/src/GetDocumentInsider/WrappedException.cs +++ /dev/null @@ -1,24 +0,0 @@ -// 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; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - internal class WrappedException : Exception - { - private readonly string _stackTrace; - - public WrappedException(string type, string message, string stackTrace) - : base(message) - { - Type = type; - _stackTrace = stackTrace; - } - - public string Type { get; } - - public override string ToString() - => _stackTrace; - } -} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs index 1e79fd9722..14c5545b2b 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs +++ b/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs @@ -4,6 +4,9 @@ using System; using System.IO; using System.Net.Http; +using System.Net.Sockets; +using System.Reflection; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; using Microsoft.Build.Framework; @@ -94,19 +97,130 @@ namespace Microsoft.Extensions.ApiDescription.Client return; } - log.LogMessage($"Downloading '{uri}' to '{destinationPath}'."); + log.LogMessage(MessageImportance.High, $"Downloading '{uri}' to '{destinationPath}'."); - using (var httpClient = new HttpClient + using (var httpClient = new HttpClient()) { - }) + await DownloadAsync(uri, destinationPath, httpClient, cancellationToken, log, timeoutSeconds); + } + } + + public static async Task DownloadAsync( + string uri, + string destinationPath, + HttpClient httpClient, + CancellationToken cancellationToken, + TaskLoggingHelper log, + int timeoutSeconds) + { + // Timeout if the response has not begun within 1 minute + httpClient.Timeout = TimeSpan.FromMinutes(1); + + var destinationExists = File.Exists(destinationPath); + var reachedCopy = false; + try { - await DownloadFileCore.DownloadAsync( - uri, - destinationPath, - httpClient, - new LogWrapper(log), - cancellationToken, - timeoutSeconds); + using (var response = await httpClient.GetAsync(uri, cancellationToken)) + { + response.EnsureSuccessStatusCode(); + cancellationToken.ThrowIfCancellationRequested(); + + using (var responseStreamTask = response.Content.ReadAsStreamAsync()) + { + var finished = await Task.WhenAny( + responseStreamTask, + Task.Delay(TimeSpan.FromSeconds(timeoutSeconds))); + + if (!ReferenceEquals(responseStreamTask, finished)) + { + throw new TimeoutException($"Download failed to complete in {timeoutSeconds} seconds."); + } + + using (var responseStream = await responseStreamTask) + { + if (destinationExists) + { + // Check hashes before using the downloaded information. + var downloadHash = GetHash(responseStream); + responseStream.Position = 0L; + + byte[] destinationHash; + using (var destinationStream = File.OpenRead(destinationPath)) + { + destinationHash = GetHash(destinationStream); + } + + var sameHashes = downloadHash.Length == destinationHash.Length; + for (var i = 0; sameHashes && i < downloadHash.Length; i++) + { + sameHashes = downloadHash[i] == destinationHash[i]; + } + + if (sameHashes) + { + log.LogMessage($"Not overwriting existing and matching file '{destinationPath}'."); + return; + } + } + else + { + // May need to create directory to hold the file. + var destinationDirectory = Path.GetDirectoryName(destinationPath); + if (!string.IsNullOrEmpty(destinationDirectory)) + { + Directory.CreateDirectory(destinationDirectory); + } + } + + // Create or overwrite the destination file. + reachedCopy = true; + using (var outStream = File.Create(destinationPath)) + { + await responseStream.CopyToAsync(outStream); + } + } + } + } + } + catch (HttpRequestException ex) when (destinationExists) + { + if (ex.InnerException is SocketException socketException) + { + log.LogWarning($"Unable to download {uri}, socket error code '{socketException.SocketErrorCode}'."); + } + else + { + log.LogWarning($"Unable to download {uri}: {ex.Message}"); + } + } + catch (Exception ex) + { + log.LogError($"Downloading '{uri}' failed."); + log.LogErrorFromException(ex, showStackTrace: true); + if (reachedCopy) + { + File.Delete(destinationPath); + } + } + } + + private static byte[] GetHash(Stream stream) + { + SHA256 algorithm; + try + { + algorithm = SHA256.Create(); + } + catch (TargetInvocationException) + { + // SHA256.Create is documented to throw this exception on FIPS-compliant machines. See + // https://msdn.microsoft.com/en-us/library/z08hz7ad Fall back to a FIPS-compliant SHA256 algorithm. + algorithm = new SHA256CryptoServiceProvider(); + } + + using (algorithm) + { + return algorithm.ComputeHash(stream); } } } diff --git a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs b/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs deleted file mode 100644 index f058620621..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/LogWrapper.cs +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using Microsoft.Build.Utilities; - -namespace Microsoft.Extensions.ApiDescription.Client -{ - internal class LogWrapper : ILogWrapper - { - private readonly TaskLoggingHelper _log; - - public LogWrapper(TaskLoggingHelper log) - { - _log = log; - } - - public void LogError(string message, params object[] messageArgs) - { - _log.LogError(message, messageArgs); - } - - public void LogError(Exception exception, bool showStackTrace) - { - _log.LogErrorFromException(exception, showStackTrace); - } - - public void LogInformational(string message, params object[] messageArgs) - { - _log.LogMessage(message, messageArgs); - } - - public void LogWarning(string message, params object[] messageArgs) - { - _log.LogWarning(message, messageArgs); - } - } -} diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj index daec0b3b26..946f3d1d16 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj @@ -8,21 +8,52 @@ MSBuild tasks and targets for code generation false - false + false + false + false $(MSBuildProjectName).nuspec - Build Tasks;msbuild;DownloadFile;GetFilenameFromUri;code generation + Build Tasks;MSBuild;Swagger;Open API;code generation; Web API client netstandard2.0;net461 - - - + + - - - + + + $(AssemblySigningCertName) + tasks/$(TargetFramework)/$(TargetFileName) + $(AssemblySigningStrongName) + + + + + + + $(AssemblySigningCertName) + tools/dotnet-getdocument.dll + $(AssemblySigningStrongName) + + + $(AssemblySigningCertName) + tools/net461/GetDocument.Insider.exe + $(AssemblySigningStrongName) + + + $(AssemblySigningCertName) + tools/net461-x86/GetDocument.Insider.exe + $(AssemblySigningStrongName) + + + $(AssemblySigningCertName) + tools/netcoreapp2.0/GetDocument.Insider.exe + $(AssemblySigningStrongName) + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec index 5b840bd379..e35a37d51c 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec +++ b/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec @@ -12,17 +12,19 @@ $owners$ $projectUrl$ - false + true $tags$ $version$ - - - - - - + + + + + + + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets b/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets deleted file mode 100644 index 5ed88a3f6c..0000000000 --- a/src/Microsoft.Extensions.ApiDescription.Client/ServiceProjectReferenceMetadata.targets +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props index d01b8044e8..0e1260da7c 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props @@ -93,11 +93,6 @@ Default is set in server project, falling back to "Microsoft.Extensions.ApiDescription.IDocumentProvider". --> - - diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets index e7c60f9017..051922a579 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets @@ -179,7 +179,7 @@ - dotnet getdocument --no-build --project %(FullPath) --output %(DocumentPath) + dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath) --output %(DocumentPath) $(DefaultDocumentGeneratorDefaultOptions) %(Command) --service %(Service) - - %(Command) --uri %(Uri) - %(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions) diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs index b376b3b9d2..b1426c057d 100644 --- a/src/dotnet-getdocument/Commands/InvokeCommand.cs +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -159,12 +159,6 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands args.Add(project.DefaultService); } - if (!(args.Contains("--uri") || string.IsNullOrEmpty(project.DefaultUri))) - { - args.Add("--uri"); - args.Add(project.DefaultUri); - } - if (_output.HasValue()) { args.Add("--output"); diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs index ce3325b7e8..f9fc0da93e 100644 --- a/src/dotnet-getdocument/Project.cs +++ b/src/dotnet-getdocument/Project.cs @@ -1,6 +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 System; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -11,7 +12,8 @@ namespace Microsoft.Extensions.ApiDescription.Client { internal class Project { - private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client.ServiceProjectReferenceMetadata"; + private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets"; + private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client." + ResourceFilename; private Project() { @@ -33,8 +35,6 @@ namespace Microsoft.Extensions.ApiDescription.Client public string DefaultService { get; private set; } - public string DefaultUri { get; private set; } - public string DepsPath { get; private set; } public string Directory { get; private set; } @@ -53,41 +53,36 @@ namespace Microsoft.Extensions.ApiDescription.Client public string RuntimeFrameworkVersion { get; private set; } + public string RuntimeIdentifier { get; private set; } + public string TargetFramework { get; private set; } public string TargetFrameworkMoniker { get; private set; } public static Project FromFile( - string file, + string projectFile, string buildExtensionsDirectory, string framework = null, string configuration = null, string runtime = null) { - Debug.Assert(!string.IsNullOrEmpty(file), "file is null or empty."); + if (string.IsNullOrEmpty(projectFile)) + { + throw new ArgumentNullException(nameof(projectFile)); + } if (string.IsNullOrEmpty(buildExtensionsDirectory)) { - buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(file), "obj"); + buildExtensionsDirectory = Path.Combine(Path.GetDirectoryName(projectFile), "obj"); } IODirectory.CreateDirectory(buildExtensionsDirectory); var assembly = typeof(Project).Assembly; - var propsPath = Path.Combine( + var targetsPath = Path.Combine( buildExtensionsDirectory, - Path.GetFileName(file) + ".ServiceProjectReferenceMetadata.props"); - using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.props")) - { - using (var output = File.OpenWrite(propsPath)) - { - Reporter.WriteVerbose(Resources.FormatWritingFile(propsPath)); - input.CopyTo(output); - } - } - - var targetsPath = Path.ChangeExtension(propsPath, ".targets"); - using (var input = assembly.GetManifestResourceStream($"{MSBuildResourceName}.targets")) + $"{Path.GetFileName(projectFile)}.{ResourceFilename}"); + using (var input = assembly.GetManifestResourceStream(MSBuildResourceName)) { using (var output = File.OpenWrite(targetsPath)) { @@ -101,32 +96,29 @@ namespace Microsoft.Extensions.ApiDescription.Client var metadataPath = Path.GetTempFileName(); try { - var propertyArg = "/property:ServiceProjectReferenceMetadataPath=" + metadataPath; - if (!string.IsNullOrEmpty(framework)) - { - propertyArg += ";TargetFramework=" + framework; - } - if (!string.IsNullOrEmpty(configuration)) - { - propertyArg += ";Configuration=" + configuration; - } - if (!string.IsNullOrEmpty(runtime)) - { - propertyArg += ";RuntimeIdentifier=" + runtime; - } - var args = new List { "msbuild", "/target:WriteServiceProjectReferenceMetadata", - propertyArg, "/verbosity:quiet", - "/nologo" + "/nologo", + $"/property:ServiceProjectReferenceMetadataPath={metadataPath}", + projectFile, }; - if (!string.IsNullOrEmpty(file)) + if (!string.IsNullOrEmpty(framework)) { - args.Add(file); + args.Add($"/property:TargetFramework={framework}"); + } + + if (!string.IsNullOrEmpty(configuration)) + { + args.Add($"/property:Configuration={configuration}"); + } + + if (!string.IsNullOrEmpty(runtime)) + { + args.Add($"/property:RuntimeIdentifier={runtime}"); } var exitCode = Exe.Run("dotnet", args); @@ -140,21 +132,20 @@ namespace Microsoft.Extensions.ApiDescription.Client } finally { - File.Delete(propsPath); File.Delete(metadataPath); File.Delete(targetsPath); } var project = new Project { + DefaultDocumentName = metadata[nameof(DefaultDocumentName)], + DefaultMethod = metadata[nameof(DefaultMethod)], + DefaultService = metadata[nameof(DefaultService)], + AssemblyName = metadata[nameof(AssemblyName)], AssemblyPath = metadata[nameof(AssemblyPath)], AssetsPath = metadata[nameof(AssetsPath)], Configuration = metadata[nameof(Configuration)], - DefaultDocumentName = metadata[nameof(DefaultDocumentName)], - DefaultMethod = metadata[nameof(DefaultMethod)], - DefaultService = metadata[nameof(DefaultService)], - DefaultUri = metadata[nameof(DefaultUri)], DepsPath = metadata[nameof(DepsPath)], Directory = metadata[nameof(Directory)], ExtensionsPath = metadata[nameof(ExtensionsPath)], @@ -164,6 +155,7 @@ namespace Microsoft.Extensions.ApiDescription.Client PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)], RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)], RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], + RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)], TargetFramework = metadata[nameof(TargetFramework)], TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], }; @@ -193,6 +185,11 @@ namespace Microsoft.Extensions.ApiDescription.Client project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath)); } + if (!Path.IsPathRooted(project.ExtensionsPath)) + { + project.ExtensionsPath = Path.GetFullPath(Path.Combine(project.Directory, project.ExtensionsPath)); + } + if (!Path.IsPathRooted(project.OutputPath)) { project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath)); diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs index eb38ce67a7..8ab2cece4f 100644 --- a/src/dotnet-getdocument/Properties/Resources.Designer.cs +++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs @@ -277,7 +277,7 @@ namespace Microsoft.Extensions.ApiDescription.Client => string.Format(CultureInfo.CurrentCulture, GetString("WritingFile"), p0); /// - /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// Project output not found. Project must be up-to-date when using this tool. /// internal static string MustBuild { @@ -285,7 +285,7 @@ namespace Microsoft.Extensions.ApiDescription.Client } /// - /// Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + /// Project output not found. Project must be up-to-date when using this tool. /// internal static string FormatMustBuild() => GetString("MustBuild"); diff --git a/src/dotnet-getdocument/Resources.resx b/src/dotnet-getdocument/Resources.resx index 7db5873e7d..9829182dc6 100644 --- a/src/dotnet-getdocument/Resources.resx +++ b/src/dotnet-getdocument/Resources.resx @@ -175,7 +175,7 @@ Writing '{0}'... - Project output not found and --no-build was specified. Project must be up-to-date when using the --no-build option. + Project output not found. Project must be up-to-date when using this tool. The file to write the result to. diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props deleted file mode 100644 index 30f045f3c0..0000000000 --- a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.props +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - $(WriteServiceProjectReferenceMetadataDependsOn) - - - - - $(WriteServiceProjectReferenceMetadataDependsOn) - - - $(WriteServiceProjectReferenceMetadataDependsOn) - - - diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets index e8840ea37b..a9a3115b94 100644 --- a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets +++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets @@ -1,20 +1,14 @@  - - - + - - - + + @@ -29,21 +23,21 @@ + - - + + - + diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index 53967535a7..370514484b 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -1,89 +1,25 @@ - - $(GenerateNuspecDependsOn);PopulateNuspec - dotnet-getdocument GetDocument Command-line Tool outside man false - true - false false - $(MSBuildProjectName).nuspec Exe - true - GetDocument;command line;command-line;tool Microsoft.Extensions.ApiDescription.Client netcoreapp2.1 - - - + - - - - + - - - - - - - - - - - - - - - <_Temporary Remove="@(Temporary)" /> - <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461" /> - <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=net461;Platform=x86" /> - <_Temporary Include="../GetDocumentInsider/GetDocumentInsider.csproj" Properties="TargetFramework=netcoreapp2.0" /> - - - - - - - <_Temporary Remove="@(_Temporary)" /> - - - - - authors=$(Authors); - copyright=$(Copyright); - description=$(Description); - iconUrl=$(PackageIconUrl); - id=$(PackageId); - InsiderNet461Output=..\GetDocumentInsider\bin\$(Configuration)\net461\publish\*; - InsiderNet461X86Output=..\GetDocumentInsider\bin\x86\$(Configuration)\net461\publish\*; - InsiderNetCoreOutput=..\GetDocumentInsider\bin\$(Configuration)\netcoreapp2.0\publish\*; - licenseUrl=$(PackageLicenseUrl); - Output=$(PublishDir)**\*; - OutputShims=$(IntermediateOutputPath)shims\**\*; - packageType=$(PackageType); - projectUrl=$(PackageProjectUrl); - repositoryCommit=$(RepositoryCommit); - repositoryUrl=$(RepositoryUrl); - SettingsFile=$(_ToolsSettingsFilePath); - tags=$(PackageTags.Replace(';', ' ')); - targetFramework=$(TargetFramework); - version=$(PackageVersion); - - - diff --git a/src/dotnet-getdocument/dotnet-getdocument.nuspec b/src/dotnet-getdocument/dotnet-getdocument.nuspec deleted file mode 100644 index 60c51f7869..0000000000 --- a/src/dotnet-getdocument/dotnet-getdocument.nuspec +++ /dev/null @@ -1,28 +0,0 @@ - - - - $id$ - $version$ - $authors$ - true - $licenseUrl$ - $projectUrl$ - $iconUrl$ - $description$ - $copyright$ - $tags$ - - - - - - - - - - - - - - - From 5cd86977eda1d06e6619e3f75bded8ee40761a2c Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Sat, 6 Oct 2018 20:07:49 -0700 Subject: [PATCH 290/316] Rename client code generation components - #8523 - main project / package --> `Microsoft.Extensions.ApiDescription.Design` - tasks assembly and namespace --> `Microsoft.Extensions.ApiDescription.Tasks` - tool namespace --> `Microsoft.Extensions.ApiDescription.Tool` - targets --> verbs e.g. `GenerateTypeScriptNSwag` and `GenerateDocumentDefault` - `@(ServiceProjectReference)` metadata -> align with common MSBuild project properties - exception: `$(MSBuildProjectExtensionsPath)`; it's readonly and `%(ProjectExtensionsPath)` is unambiguous - use `%(ProjectExtensionsPath)` - also add `%(Targets)` metadata and remove unused `%(ProjectRuntimeIdentifier)` - `@( align with MSBuild project properties - exceptions: `$(MSBuildProjectDirectory)`, `$(MSBuildProjectExtensionsPath)` and `$(MSBuildProjectName)` - readonly properties and names already unambiguous --- Mvc.NoFun.sln | 2 +- Mvc.sln | 2 +- NuGetPackageVerifier.json | 2 +- src/GetDocumentInsider/AnsiConsole.cs | 2 +- src/GetDocumentInsider/AnsiConstants.cs | 2 +- src/GetDocumentInsider/AnsiTextWriter.cs | 2 +- src/GetDocumentInsider/CommandException.cs | 2 +- .../Commands/CommandBase.cs | 2 +- .../Commands/GetDocumentCommand.cs | 2 +- .../Commands/GetDocumentCommandContext.cs | 2 +- .../Commands/GetDocumentCommandWorker.cs | 2 +- .../Commands/HelpCommandBase.cs | 2 +- .../Commands/ProjectCommandBase.cs | 2 +- .../GetDocumentInsider.csproj | 2 +- src/GetDocumentInsider/ProductInfo.cs | 2 +- src/GetDocumentInsider/Program.cs | 4 +- .../Properties/Resources.Designer.cs | 4 +- src/GetDocumentInsider/Reporter.cs | 4 +- .../DownloadFile.cs | 2 +- .../GetCurrentItems.cs | 2 +- .../GetFileReferenceMetadata.cs | 2 +- .../GetProjectReferenceMetadata.cs | 2 +- .../GetUriReferenceMetadata.cs | 2 +- .../MetadataSerializer.cs | 2 +- ...t.Extensions.ApiDescription.Design.csproj} | 3 + ...t.Extensions.ApiDescription.Design.nuspec} | 4 +- .../Properties/Resources.Designer.cs | 4 +- .../Resources.resx | 0 ...ft.Extensions.ApiDescription.Design.props} | 53 ++++--- ....Extensions.ApiDescription.Design.targets} | 149 +++++++++--------- ....Extensions.ApiDescription.Design.targets} | 4 +- .../Commands/InvokeCommand.cs | 40 ++--- src/dotnet-getdocument/Exe.cs | 2 +- src/dotnet-getdocument/Program.cs | 4 +- src/dotnet-getdocument/Project.cs | 113 +++++++------ src/dotnet-getdocument/ProjectOptions.cs | 18 ++- .../Properties/Resources.Designer.cs | 24 +-- src/dotnet-getdocument/Resources.resx | 8 +- .../ServiceProjectReferenceMetadata.targets | 16 +- .../dotnet-getdocument.csproj | 2 +- 40 files changed, 266 insertions(+), 232 deletions(-) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/DownloadFile.cs (99%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/GetCurrentItems.cs (94%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/GetFileReferenceMetadata.cs (98%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/GetProjectReferenceMetadata.cs (98%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/GetUriReferenceMetadata.cs (98%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/MetadataSerializer.cs (99%) rename src/{Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj => Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj} (95%) rename src/{Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec => Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec} (92%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/Properties/Resources.Designer.cs (96%) rename src/{Microsoft.Extensions.ApiDescription.Client => Microsoft.Extensions.ApiDescription.Design}/Resources.resx (100%) rename src/{Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props => Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props} (71%) rename src/{Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.targets => Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.targets} (65%) rename src/{Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets => Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets} (67%) diff --git a/Mvc.NoFun.sln b/Mvc.NoFun.sln index 0167992a7f..d67abd6f60 100644 --- a/Mvc.NoFun.sln +++ b/Mvc.NoFun.sln @@ -121,7 +121,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\d EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Mvc.sln b/Mvc.sln index ce10a95245..dcd83f09ca 100644 --- a/Mvc.sln +++ b/Mvc.sln @@ -182,7 +182,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "dotnet-getdocument", "src\d EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GetDocumentInsider", "src\GetDocumentInsider\GetDocumentInsider.csproj", "{2F683CF8-B055-46AE-BF83-9D1307F8D45F}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Client", "src\Microsoft.Extensions.ApiDescription.Client\Microsoft.Extensions.ApiDescription.Client.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.ApiDescription.Design", "src\Microsoft.Extensions.ApiDescription.Design\Microsoft.Extensions.ApiDescription.Design.csproj", "{34E3C302-B767-40C8-B538-3EE2BD4000C4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index 079b7cef51..a1e9573ee3 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -4,7 +4,7 @@ "DefaultCompositeRule" ], "packages": { - "Microsoft.Extensions.ApiDescription.Client": { + "Microsoft.Extensions.ApiDescription.Design": { "Exclusions": { "BUILD_ITEMS_FRAMEWORK": { "*": "Package includes tool with different target frameworks." diff --git a/src/GetDocumentInsider/AnsiConsole.cs b/src/GetDocumentInsider/AnsiConsole.cs index c3f514bde4..306b4ff452 100644 --- a/src/GetDocumentInsider/AnsiConsole.cs +++ b/src/GetDocumentInsider/AnsiConsole.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal class AnsiConsole { diff --git a/src/GetDocumentInsider/AnsiConstants.cs b/src/GetDocumentInsider/AnsiConstants.cs index 48f9ca2410..b54e15b751 100644 --- a/src/GetDocumentInsider/AnsiConstants.cs +++ b/src/GetDocumentInsider/AnsiConstants.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. -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class AnsiConstants { diff --git a/src/GetDocumentInsider/AnsiTextWriter.cs b/src/GetDocumentInsider/AnsiTextWriter.cs index 471d9cf124..066f711e27 100644 --- a/src/GetDocumentInsider/AnsiTextWriter.cs +++ b/src/GetDocumentInsider/AnsiTextWriter.cs @@ -6,7 +6,7 @@ using System.Diagnostics; using System.IO; using System.Text.RegularExpressions; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal class AnsiTextWriter { diff --git a/src/GetDocumentInsider/CommandException.cs b/src/GetDocumentInsider/CommandException.cs index 18680f9593..c1437b038b 100644 --- a/src/GetDocumentInsider/CommandException.cs +++ b/src/GetDocumentInsider/CommandException.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal class CommandException : Exception { diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/GetDocumentInsider/Commands/CommandBase.cs index 5ca14744cb..ac9a4b1a37 100644 --- a/src/GetDocumentInsider/Commands/CommandBase.cs +++ b/src/GetDocumentInsider/Commands/CommandBase.cs @@ -3,7 +3,7 @@ using Microsoft.DotNet.Cli.CommandLine; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal abstract class CommandBase { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs index cd45255f09..bf80df802b 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -10,7 +10,7 @@ using System.Runtime.Loader; #endif using Microsoft.DotNet.Cli.CommandLine; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class GetDocumentCommand : ProjectCommandBase { diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs index c4fc0b6e45..208139c12f 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -3,7 +3,7 @@ using System; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { [Serializable] public class GetDocumentCommandContext diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index b0434e453c..752d65861f 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -7,7 +7,7 @@ using System.Reflection; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class GetDocumentCommandWorker { diff --git a/src/GetDocumentInsider/Commands/HelpCommandBase.cs b/src/GetDocumentInsider/Commands/HelpCommandBase.cs index 3f80564d54..55e84272ac 100644 --- a/src/GetDocumentInsider/Commands/HelpCommandBase.cs +++ b/src/GetDocumentInsider/Commands/HelpCommandBase.cs @@ -3,7 +3,7 @@ using Microsoft.DotNet.Cli.CommandLine; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class HelpCommandBase : CommandBase { diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs index 7e6e49c750..8e60d9603f 100644 --- a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs +++ b/src/GetDocumentInsider/Commands/ProjectCommandBase.cs @@ -3,7 +3,7 @@ using Microsoft.DotNet.Cli.CommandLine; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal abstract class ProjectCommandBase : HelpCommandBase { diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/GetDocumentInsider/GetDocumentInsider.csproj index b2c4fed5c6..03d6440c1f 100644 --- a/src/GetDocumentInsider/GetDocumentInsider.csproj +++ b/src/GetDocumentInsider/GetDocumentInsider.csproj @@ -4,7 +4,7 @@ GetDocument Command-line Tool inside man false Exe - Microsoft.Extensions.ApiDescription.Client + Microsoft.Extensions.ApiDescription.Tool netcoreapp2.0;net461 diff --git a/src/GetDocumentInsider/ProductInfo.cs b/src/GetDocumentInsider/ProductInfo.cs index 8db001423a..c57bc65d10 100644 --- a/src/GetDocumentInsider/ProductInfo.cs +++ b/src/GetDocumentInsider/ProductInfo.cs @@ -3,7 +3,7 @@ using System.Reflection; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class ProductInfo { diff --git a/src/GetDocumentInsider/Program.cs b/src/GetDocumentInsider/Program.cs index af1bdc298a..6d144dc5d5 100644 --- a/src/GetDocumentInsider/Program.cs +++ b/src/GetDocumentInsider/Program.cs @@ -4,9 +4,9 @@ using System; using System.Text; using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Commands; +using Microsoft.Extensions.ApiDescription.Tool.Commands; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class Program { diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs index c9576ef8c0..eaec18f2fe 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.cs +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -1,5 +1,5 @@ // -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { using System.Globalization; using System.Reflection; @@ -8,7 +8,7 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// The assembly to use. diff --git a/src/GetDocumentInsider/Reporter.cs b/src/GetDocumentInsider/Reporter.cs index abfec580fb..9a589fcc67 100644 --- a/src/GetDocumentInsider/Reporter.cs +++ b/src/GetDocumentInsider/Reporter.cs @@ -3,9 +3,9 @@ using System; using System.Linq; -using static Microsoft.Extensions.ApiDescription.Client.AnsiConstants; +using static Microsoft.Extensions.ApiDescription.Tool.AnsiConstants; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class Reporter { diff --git a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs b/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs similarity index 99% rename from src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs rename to src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs index 14c5545b2b..177f405ea1 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/DownloadFile.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs @@ -14,7 +14,7 @@ using Microsoft.Build.Utilities; using Task = System.Threading.Tasks.Task; using Utilities = Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Downloads a file. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs b/src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs similarity index 94% rename from src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs rename to src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs index 975e716d64..97ea236f2c 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetCurrentItems.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs @@ -4,7 +4,7 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Restore s from given property value. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs similarity index 98% rename from src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs rename to src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs index 54ae8b7500..70ac4d847f 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetFileReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs @@ -7,7 +7,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Adds or corrects ClassName, Namespace and OutputPath metadata in ServiceFileReference items. Also stores final diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs similarity index 98% rename from src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs rename to src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs index 0c682107e5..a4ad42abe7 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetProjectReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs @@ -6,7 +6,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Adds or corrects DocumentPath and project-related metadata in ServiceProjectReference items. Also stores final diff --git a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs b/src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs similarity index 98% rename from src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs rename to src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs index 6870d2a337..922359cb36 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/GetUriReferenceMetadata.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs @@ -7,7 +7,7 @@ using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Adds or corrects DocumentPath metadata in ServiceUriReference items. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs b/src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs similarity index 99% rename from src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs rename to src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs index 3f430380a0..331bac617a 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/MetadataSerializer.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs @@ -6,7 +6,7 @@ using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Utilities; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { /// /// Utility methods to serialize and deserialize metadata. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj similarity index 95% rename from src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj rename to src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj index 946f3d1d16..164a547cc4 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj @@ -6,13 +6,16 @@ true + Microsoft.Extensions.ApiDescription.Tasks MSBuild tasks and targets for code generation false false false false $(MSBuildProjectName).nuspec + $(MSBuildProjectName) Build Tasks;MSBuild;Swagger;Open API;code generation; Web API client + $(AssemblyName) netstandard2.0;net461 diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec similarity index 92% rename from src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec rename to src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec index e35a37d51c..95ca2bcfd8 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Microsoft.Extensions.ApiDescription.Client.nuspec +++ b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec @@ -20,8 +20,8 @@ - - + + diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs b/src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs similarity index 96% rename from src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs rename to src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs index 27049a4842..b72e347e21 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/Properties/Resources.Designer.cs +++ b/src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs @@ -1,5 +1,5 @@ // -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tasks { using System.Globalization; using System.Reflection; @@ -8,7 +8,7 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Tasks.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// Multiple items have OutputPath='{0}'. All ServiceFileReference, ServiceProjectReference and ServiceUriReference items must have unique OutputPath metadata. diff --git a/src/Microsoft.Extensions.ApiDescription.Client/Resources.resx b/src/Microsoft.Extensions.ApiDescription.Design/Resources.resx similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Client/Resources.resx rename to src/Microsoft.Extensions.ApiDescription.Design/Resources.resx diff --git a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props similarity index 71% rename from src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props rename to src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props index 0e1260da7c..3a3177cdc9 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/build/Microsoft.Extensions.ApiDescription.Client.props +++ b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props @@ -1,16 +1,19 @@  - <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 - <_ApiDescriptionTasksAssemblyTarget Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 - <_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Client.dll + <_ApiDescriptionTasksAssemblyTarget + Condition="'$(MSBuildRuntimeType)' == 'Core'">netstandard2.0 + <_ApiDescriptionTasksAssemblyTarget + Condition="'$(MSBuildRuntimeType)' != 'Core'">net461 + <_ApiDescriptionTasksAssemblyPath>$(MSBuildThisFileDirectory)/../tasks/$(_ApiDescriptionTasksAssemblyTarget)/Microsoft.Extensions.ApiDescription.Tasks.dll <_ApiDescriptionTasksAssemblyTarget /> - + Default - - - + - - - - + + + + + + - + - + - - _ServiceProjectReferenceGenerator_GetTargetFramework; - _ServiceProjectReferenceGenerator_GetProjectTargetPath; - _ServiceProjectReferenceGenerator_GetMetadata; - _ServiceProjectReferenceGenerator_Build; - _ServiceProjectReferenceGenerator_Core; - _ServiceProjectReferenceGenerator_SetMetadata - - - _ServiceUriReferenceGenerator_GetMetadata; - _ServiceUriReferenceGenerator_Core - - - ServiceProjectReferenceGenerator; - ServiceUriReferenceGenerator; - _ServiceFileReferenceGenerator_GetMetadata; - _ServiceFileReferenceGenerator_Core; - _ServiceFileReferenceGenerator_SetMetadata - + + _GetTargetFrameworkForServiceProjectReferences; + _GetTargetPathForServiceProjectReferences; + _GetMetadataForServiceProjectReferences; + _BuildServiceProjectReferences; + _GenerateServiceProjectReferenceDocuments; + _CreateFileItemsForServiceProjectReferences + + + _GetMetadataForServiceUriReferences; + _GenerateServiceUriReferenceDocuments + + + GenerateServiceProjectReferenceDocuments; + GenerateServiceUriReferenceDocuments; + _GetMetadataForServiceFileReferences; + _GenerateServiceFileReferenceCodes; + _CreateCompileItemsForServiceFileReferences + - @@ -56,8 +56,7 @@ - $(_TargetFramework) + $(_TargetFramework) <_Temporary Remove="@(_Temporary)" /> @@ -70,16 +69,16 @@ - <_FullPath>%(ServiceProjectReference.FullPath) - <_TargetFramework>%(ServiceProjectReference.ProjectTargetFramework) + <_TargetFramework>%(ServiceProjectReference.TargetFramework) <_Temporary Remove="@(_Temporary)" /> @@ -95,25 +94,25 @@ - <_ProjectTargetPath>%(_Temporary.FullPath) + <_TargetPath>%(_Temporary.FullPath) - $(_ProjectTargetPath) + Condition="'%(FullPath)' == '$(_FullPath)' AND '%(TargetFramework)' == '$(_TargetFramework)'"> + $(_TargetPath) <_Temporary Remove="@(_Temporary)" /> <_FullPath /> - <_ProjectTargetPath /> + <_TargetPath /> <_TargetFramework /> - + <_Temporary Remove="@(_Temporary)" /> @@ -132,38 +131,40 @@ - + Outputs="%(TargetPath)"> + Targets="%(Targets)" /> - + - + - + + Targets="_GenerateServiceProjectReferenceDocument" /> - - + + - + %(ServiceProjectReference.FullPath) @@ -171,22 +172,22 @@ - + - + - + - dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath) --output %(DocumentPath) - $(DefaultDocumentGeneratorDefaultOptions) - $(Configuration) + dotnet $(MSBuildThisFileDirectory)/../tools/dotnet-getdocument.dll --project %(FullPath) + $(Configuration) + $(GenerateDefaultDocumentDefaultOptions) - %(Command) --framework %(ProjectTargetFramework) + %(Command) --framework %(TargetFramework) --output %(DocumentPath) %(Command) --method %(Method) @@ -195,11 +196,15 @@ %(Command) --service %(Service) - %(Command) --configuration %(ProjectConfiguration) %(DefaultDocumentGeneratorOptions) + %(Command) --projectExtensionsPath %(ProjectExtensionsPath) + + + %(Command) --configuration %(Configuration) %(GenerateDefaultDocumentOptions) - + @@ -207,7 +212,7 @@ - + <_Temporary Remove="@(_Temporary)" /> @@ -223,12 +228,12 @@ - - + - + @@ -237,11 +242,12 @@ - + - + <_Temporary Remove="@(_Temporary)" /> @@ -260,28 +266,27 @@ - + - + - + + Targets="_GenerateServiceFileReferenceCode" /> - + <_Files Remove="@(_Files)" /> @@ -324,7 +329,7 @@ - + DependsOnTargets="$(GenerateServiceFileReferenceCodesDependsOn)" /> diff --git a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets b/src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets similarity index 67% rename from src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets rename to src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets index a9c3d53836..af5d08a6bb 100644 --- a/src/Microsoft.Extensions.ApiDescription.Client/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Client.targets +++ b/src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets @@ -1,8 +1,8 @@  - + diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/dotnet-getdocument/Commands/InvokeCommand.cs index b1426c057d..4d24919c08 100644 --- a/src/dotnet-getdocument/Commands/InvokeCommand.cs +++ b/src/dotnet-getdocument/Commands/InvokeCommand.cs @@ -10,30 +10,30 @@ using Microsoft.DotNet.Cli.CommandLine; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -namespace Microsoft.Extensions.ApiDescription.Client.Commands +namespace Microsoft.Extensions.ApiDescription.Tool.Commands { internal class InvokeCommand : HelpCommandBase { private const string InsideManName = "GetDocument.Insider"; + private IList _args; private CommandOption _configuration; - private CommandOption _framework; - private CommandOption _msbuildprojectextensionspath; private CommandOption _output; private CommandOption _project; + private CommandOption _projectExtensionsPath; private CommandOption _runtime; - private IList _args; + private CommandOption _targetFramework; public override void Configure(CommandLineApplication command) { var options = new ProjectOptions(); options.Configure(command); - _project = options.Project; - _framework = options.Framework; _configuration = options.Configuration; + _project = options.Project; + _projectExtensionsPath = options.ProjectExtensionsPath; _runtime = options.Runtime; - _msbuildprojectextensionspath = options.MSBuildProjectExtensionsPath; + _targetFramework = options.TargetFramework; _output = command.Option("--output ", Resources.OutputDescription); command.VersionOption("--version", ProductInfo.GetVersion); @@ -52,11 +52,11 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands var project = Project.FromFile( projectFile, - _msbuildprojectextensionspath.Value(), - _framework.Value(), + _projectExtensionsPath.Value(), + _targetFramework.Value(), _configuration.Value(), _runtime.Value()); - if (!File.Exists(project.AssemblyPath)) + if (!File.Exists(project.TargetPath)) { throw new CommandException(Resources.MustBuild); } @@ -95,16 +95,16 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands if (targetFramework.Version < new Version(2, 0)) { throw new CommandException( - Resources.FormatNETCoreApp1Project(project.Name, targetFramework.Version)); + Resources.FormatNETCoreApp1Project(project.ProjectName, targetFramework.Version)); } args.Add("exec"); args.Add("--depsFile"); - args.Add(project.DepsPath); + args.Add(project.ProjectDepsFilePath); - if (!string.IsNullOrEmpty(project.AssetsPath)) + if (!string.IsNullOrEmpty(project.ProjectAssetsFile)) { - using (var reader = new JsonTextReader(File.OpenText(project.AssetsPath))) + using (var reader = new JsonTextReader(File.OpenText(project.ProjectAssetsFile))) { var projectAssets = JToken.ReadFrom(reader); var packageFolders = projectAssets["packageFolders"] @@ -119,10 +119,10 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands } } - if (File.Exists(project.RuntimeConfigPath)) + if (File.Exists(project.ProjectRuntimeConfigFilePath)) { args.Add("--runtimeConfig"); - args.Add(project.RuntimeConfigPath); + args.Add(project.ProjectRuntimeConfigFilePath); } else if (!string.IsNullOrEmpty(project.RuntimeFrameworkVersion)) { @@ -134,16 +134,16 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands break; case ".NETStandard": - throw new CommandException(Resources.FormatNETStandardProject(project.Name)); + throw new CommandException(Resources.FormatNETStandardProject(project.ProjectName)); default: throw new CommandException( - Resources.FormatUnsupportedFramework(project.Name, targetFramework.Identifier)); + Resources.FormatUnsupportedFramework(project.ProjectName, targetFramework.Identifier)); } args.AddRange(_args); args.Add("--assembly"); - args.Add(project.AssemblyPath); + args.Add(project.TargetPath); args.Add("--tools-directory"); args.Add(toolsDirectory); @@ -180,7 +180,7 @@ namespace Microsoft.Extensions.ApiDescription.Client.Commands args.Add("--prefix-output"); } - return Exe.Run(executable, args, project.Directory); + return Exe.Run(executable, args, project.ProjectDirectory); } finally { diff --git a/src/dotnet-getdocument/Exe.cs b/src/dotnet-getdocument/Exe.cs index 32595ce9ab..08fc4217ba 100644 --- a/src/dotnet-getdocument/Exe.cs +++ b/src/dotnet-getdocument/Exe.cs @@ -5,7 +5,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Text; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class Exe { diff --git a/src/dotnet-getdocument/Program.cs b/src/dotnet-getdocument/Program.cs index 4e1f7fb5ca..94958f2840 100644 --- a/src/dotnet-getdocument/Program.cs +++ b/src/dotnet-getdocument/Program.cs @@ -3,9 +3,9 @@ using System; using Microsoft.DotNet.Cli.CommandLine; -using Microsoft.Extensions.ApiDescription.Client.Commands; +using Microsoft.Extensions.ApiDescription.Tool.Commands; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal static class Program { diff --git a/src/dotnet-getdocument/Project.cs b/src/dotnet-getdocument/Project.cs index f9fc0da93e..dc4a06ea36 100644 --- a/src/dotnet-getdocument/Project.cs +++ b/src/dotnet-getdocument/Project.cs @@ -8,12 +8,12 @@ using System.IO; using System.Linq; using IODirectory = System.IO.Directory; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal class Project { private const string ResourceFilename = "ServiceProjectReferenceMetadata.targets"; - private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Client." + ResourceFilename; + private const string MSBuildResourceName = "Microsoft.Extensions.ApiDescription.Tool." + ResourceFilename; private Project() { @@ -21,35 +21,33 @@ namespace Microsoft.Extensions.ApiDescription.Client public string AssemblyName { get; private set; } - public string AssemblyPath { get; private set; } - - public string AssetsPath { get; private set; } + public string ConfigPath { get; private set; } public string Configuration { get; private set; } - public string ConfigPath { get; private set; } - public string DefaultDocumentName { get; private set; } public string DefaultMethod { get; private set; } public string DefaultService { get; private set; } - public string DepsPath { get; private set; } - - public string Directory { get; private set; } - - public string ExtensionsPath { get; private set; } - - public string Name { get; private set; } - public string OutputPath { get; private set; } public string Platform { get; private set; } public string PlatformTarget { get; private set; } - public string RuntimeConfigPath { get; private set; } + public string ProjectAssetsFile { get; private set; } + + public string ProjectDepsFilePath { get; private set; } + + public string ProjectDirectory { get; private set; } + + public string ProjectExtensionsPath { get; private set; } + + public string ProjectName { get; private set; } + + public string ProjectRuntimeConfigFilePath { get; private set; } public string RuntimeFrameworkVersion { get; private set; } @@ -59,6 +57,8 @@ namespace Microsoft.Extensions.ApiDescription.Client public string TargetFrameworkMoniker { get; private set; } + public string TargetPath { get; private set; } + public static Project FromFile( string projectFile, string buildExtensionsDirectory, @@ -127,7 +127,9 @@ namespace Microsoft.Extensions.ApiDescription.Client throw new CommandException(Resources.GetMetadataFailed); } - metadata = File.ReadLines(metadataPath).Select(l => l.Split(new[] { ':' }, 2)) + metadata = File + .ReadLines(metadataPath) + .Select(l => l.Split(new[] { ':' }, 2)) .ToDictionary(s => s[0], s => s[1].TrimStart()); } finally @@ -143,79 +145,88 @@ namespace Microsoft.Extensions.ApiDescription.Client DefaultService = metadata[nameof(DefaultService)], AssemblyName = metadata[nameof(AssemblyName)], - AssemblyPath = metadata[nameof(AssemblyPath)], - AssetsPath = metadata[nameof(AssetsPath)], Configuration = metadata[nameof(Configuration)], - DepsPath = metadata[nameof(DepsPath)], - Directory = metadata[nameof(Directory)], - ExtensionsPath = metadata[nameof(ExtensionsPath)], - Name = metadata[nameof(Name)], OutputPath = metadata[nameof(OutputPath)], Platform = metadata[nameof(Platform)], PlatformTarget = metadata[nameof(PlatformTarget)] ?? metadata[nameof(Platform)], - RuntimeConfigPath = metadata[nameof(RuntimeConfigPath)], + ProjectAssetsFile = metadata[nameof(ProjectAssetsFile)], + ProjectDepsFilePath = metadata[nameof(ProjectDepsFilePath)], + ProjectDirectory = metadata[nameof(ProjectDirectory)], + ProjectExtensionsPath = metadata[nameof(ProjectExtensionsPath)], + ProjectName = metadata[nameof(ProjectName)], + ProjectRuntimeConfigFilePath = metadata[nameof(ProjectRuntimeConfigFilePath)], RuntimeFrameworkVersion = metadata[nameof(RuntimeFrameworkVersion)], RuntimeIdentifier = metadata[nameof(RuntimeIdentifier)], TargetFramework = metadata[nameof(TargetFramework)], TargetFrameworkMoniker = metadata[nameof(TargetFrameworkMoniker)], + TargetPath = metadata[nameof(TargetPath)], }; - if (string.IsNullOrEmpty(project.AssemblyPath)) - { - throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(AssemblyPath), "TargetPath")); - } - - if (string.IsNullOrEmpty(project.Directory)) - { - throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(Directory), "ProjectDir")); - } - if (string.IsNullOrEmpty(project.OutputPath)) { - throw new CommandException(Resources.FormatGetMetadataValueFailed(nameof(OutputPath), "OutDir")); + throw new CommandException( + Resources.FormatGetMetadataValueFailed(nameof(OutputPath), nameof(OutputPath))); } - if (!Path.IsPathRooted(project.Directory)) + if (string.IsNullOrEmpty(project.ProjectDirectory)) { - project.Directory = Path.GetFullPath(Path.Combine(IODirectory.GetCurrentDirectory(), project.Directory)); + throw new CommandException( + Resources.FormatGetMetadataValueFailed(nameof(ProjectDirectory), "MSBuildProjectDirectory")); } - if (!Path.IsPathRooted(project.AssemblyPath)) + if (string.IsNullOrEmpty(project.TargetPath)) { - project.AssemblyPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssemblyPath)); + throw new CommandException( + Resources.FormatGetMetadataValueFailed(nameof(TargetPath), nameof(TargetPath))); } - if (!Path.IsPathRooted(project.ExtensionsPath)) + if (!Path.IsPathRooted(project.ProjectDirectory)) { - project.ExtensionsPath = Path.GetFullPath(Path.Combine(project.Directory, project.ExtensionsPath)); + project.OutputPath = Path.GetFullPath( + Path.Combine(IODirectory.GetCurrentDirectory(), project.ProjectDirectory)); } if (!Path.IsPathRooted(project.OutputPath)) { - project.OutputPath = Path.GetFullPath(Path.Combine(project.Directory, project.OutputPath)); + project.OutputPath = Path.GetFullPath(Path.Combine(project.ProjectDirectory, project.OutputPath)); } - // Some document generation tools support non-ASP.NET Core projects. - // Thus any of the remaining properties may be empty. - if (!(string.IsNullOrEmpty(project.AssetsPath) || Path.IsPathRooted(project.AssetsPath))) + if (!Path.IsPathRooted(project.ProjectExtensionsPath)) { - project.AssetsPath = Path.GetFullPath(Path.Combine(project.Directory, project.AssetsPath)); + project.ProjectExtensionsPath = Path.GetFullPath( + Path.Combine(project.ProjectDirectory, project.ProjectExtensionsPath)); } - var configPath = $"{project.AssemblyPath}.config"; + if (!Path.IsPathRooted(project.TargetPath)) + { + project.TargetPath = Path.GetFullPath(Path.Combine(project.OutputPath, project.TargetPath)); + } + + // Some document generation tools support non-ASP.NET Core projects. Any of the remaining properties may + // thus be null empty. + var configPath = $"{project.TargetPath}.config"; if (File.Exists(configPath)) { project.ConfigPath = configPath; } - if (!(string.IsNullOrEmpty(project.DepsPath) || Path.IsPathRooted(project.DepsPath))) + if (!(string.IsNullOrEmpty(project.ProjectAssetsFile) || Path.IsPathRooted(project.ProjectAssetsFile))) { - project.DepsPath = Path.GetFullPath(Path.Combine(project.Directory, project.DepsPath)); + project.ProjectAssetsFile = Path.GetFullPath( + Path.Combine(project.ProjectDirectory, project.ProjectAssetsFile)); } - if (!(string.IsNullOrEmpty(project.RuntimeConfigPath) || Path.IsPathRooted(project.RuntimeConfigPath))) + if (!(string.IsNullOrEmpty(project.ProjectDepsFilePath) || Path.IsPathRooted(project.ProjectDepsFilePath))) { - project.RuntimeConfigPath = Path.GetFullPath(Path.Combine(project.Directory, project.RuntimeConfigPath)); + project.ProjectDepsFilePath = Path.GetFullPath( + Path.Combine(project.ProjectDirectory, project.ProjectDepsFilePath)); + } + + if (!(string.IsNullOrEmpty(project.ProjectRuntimeConfigFilePath) || + Path.IsPathRooted(project.ProjectRuntimeConfigFilePath))) + { + project.ProjectRuntimeConfigFilePath = Path.GetFullPath( + Path.Combine(project.OutputPath, project.ProjectRuntimeConfigFilePath)); } return project; diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/dotnet-getdocument/ProjectOptions.cs index f3b7d1148e..c235c2191a 100644 --- a/src/dotnet-getdocument/ProjectOptions.cs +++ b/src/dotnet-getdocument/ProjectOptions.cs @@ -3,27 +3,29 @@ using Microsoft.DotNet.Cli.CommandLine; -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { internal class ProjectOptions { + public CommandOption Configuration { get; private set; } + public CommandOption Project { get; private set; } - public CommandOption Framework { get; private set; } - - public CommandOption Configuration { get; private set; } + public CommandOption ProjectExtensionsPath { get; private set; } public CommandOption Runtime { get; private set; } - public CommandOption MSBuildProjectExtensionsPath { get; private set; } + public CommandOption TargetFramework { get; private set; } public void Configure(CommandLineApplication command) { - Project = command.Option("-p|--project ", Resources.ProjectDescription); - Framework = command.Option("--framework ", Resources.FrameworkDescription); Configuration = command.Option("--configuration ", Resources.ConfigurationDescription); + Project = command.Option("-p|--project ", Resources.ProjectDescription); + ProjectExtensionsPath = command.Option( + "--projectExtensionsPath ", + Resources.ProjectExtensionsPathDescription); Runtime = command.Option("--runtime ", Resources.RuntimeDescription); - MSBuildProjectExtensionsPath = command.Option("--msbuildprojectextensionspath ", Resources.ProjectExtensionsDescription); + TargetFramework = command.Option("--framework ", Resources.TargetFrameworkDescription); } } } diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/dotnet-getdocument/Properties/Resources.Designer.cs index 8ab2cece4f..f2b9084ae1 100644 --- a/src/dotnet-getdocument/Properties/Resources.Designer.cs +++ b/src/dotnet-getdocument/Properties/Resources.Designer.cs @@ -1,5 +1,5 @@ // -namespace Microsoft.Extensions.ApiDescription.Client +namespace Microsoft.Extensions.ApiDescription.Tool { using System.Globalization; using System.Reflection; @@ -8,7 +8,7 @@ namespace Microsoft.Extensions.ApiDescription.Client internal static class Resources { private static readonly ResourceManager _resourceManager - = new ResourceManager("Microsoft.Extensions.ApiDescription.Client.Resources", typeof(Resources).GetTypeInfo().Assembly); + = new ResourceManager("Microsoft.Extensions.ApiDescription.Tool.Resources", typeof(Resources).GetTypeInfo().Assembly); /// /// The configuration to use. @@ -41,19 +41,19 @@ namespace Microsoft.Extensions.ApiDescription.Client /// /// The target framework. /// - internal static string FrameworkDescription + internal static string TargetFrameworkDescription { - get => GetString("FrameworkDescription"); + get => GetString("TargetFrameworkDescription"); } /// /// The target framework. /// - internal static string FormatFrameworkDescription() - => GetString("FrameworkDescription"); + internal static string FormatTargetFrameworkDescription() + => GetString("TargetFrameworkDescription"); /// - /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. /// internal static string GetMetadataFailed { @@ -61,7 +61,7 @@ namespace Microsoft.Extensions.ApiDescription.Client } /// - /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + /// Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. /// internal static string FormatGetMetadataFailed() => GetString("GetMetadataFailed"); @@ -195,16 +195,16 @@ namespace Microsoft.Extensions.ApiDescription.Client /// /// The MSBuild project extensions path. Defaults to "obj". /// - internal static string ProjectExtensionsDescription + internal static string ProjectExtensionsPathDescription { - get => GetString("ProjectExtensionsDescription"); + get => GetString("ProjectExtensionsPathDescription"); } /// /// The MSBuild project extensions path. Defaults to "obj". /// - internal static string FormatProjectExtensionsDescription() - => GetString("ProjectExtensionsDescription"); + internal static string FormatProjectExtensionsPathDescription() + => GetString("ProjectExtensionsPathDescription"); /// /// The runtime identifier to use. diff --git a/src/dotnet-getdocument/Resources.resx b/src/dotnet-getdocument/Resources.resx index 9829182dc6..d87157fff2 100644 --- a/src/dotnet-getdocument/Resources.resx +++ b/src/dotnet-getdocument/Resources.resx @@ -123,11 +123,11 @@ dotnet-getdocument - + The target framework. - Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --msbuildprojectextensionspath option. + Unable to retrieve project metadata. If you are using custom BaseIntermediateOutputPath or MSBuildProjectExtensionsPath values, use the --projectExtensionsPath option. More than one project was found in the current working directory. Use the --project option. @@ -156,7 +156,7 @@ The project to use. - + The MSBuild project extensions path. Defaults to "obj". @@ -181,6 +181,6 @@ The file to write the result to. - Unable to retrieve '{0}' project metadata. Ensure '{1}' is set. + Unable to retrieve '{0}' project metadata. Ensure '$({1})' is set. diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets index a9a3115b94..177d950c9e 100644 --- a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets +++ b/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets @@ -11,21 +11,21 @@ - - - - - - - + - + + + + + + + diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/dotnet-getdocument/dotnet-getdocument.csproj index 370514484b..ebda3eb047 100644 --- a/src/dotnet-getdocument/dotnet-getdocument.csproj +++ b/src/dotnet-getdocument/dotnet-getdocument.csproj @@ -5,7 +5,7 @@ false false Exe - Microsoft.Extensions.ApiDescription.Client + Microsoft.Extensions.ApiDescription.Tool netcoreapp2.1 From f6fc34aff93ef2c1a86f99b34b630e6cf721d765 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 10 Oct 2018 10:23:59 -0700 Subject: [PATCH 291/316] React to CORS changes --- build/dependencies.props | 2 +- .../CorsTestsBase.cs | 64 +++++++++++++++---- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index 4e7f5dcd1f..9336c20f8d 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -28,7 +28,7 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-a-preview3-22cors-16556 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs index 01e9e900eb..3d5be3b90a 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs @@ -102,7 +102,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests [InlineData("HEAD")] [InlineData("POST")] [InlineData("PUT")] - public async Task PolicyFailed_Disallows_PreFlightRequest(string method) + public async Task OriginMatched_ReturnsHeaders(string method) { // Arrange var request = new HttpRequestMessage( @@ -120,7 +120,18 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Assert // MVC applied the policy and since that did not pass, there were no access control headers. Assert.Equal(HttpStatusCode.OK, response.StatusCode); - Assert.Empty(response.Headers); + Assert.Collection( + response.Headers.OrderBy(h => h.Key), + h => + { + Assert.Equal(CorsConstants.AccessControlAllowMethods, h.Key); + Assert.Equal(new[] { "GET,POST,HEAD" }, h.Value); + }, + h => + { + Assert.Equal(CorsConstants.AccessControlAllowOrigin, h.Key); + Assert.Equal(new[] { "*" }, h.Value); + }); // It should short circuit and hence no result. var content = await response.Content.ReadAsStringAsync(); @@ -146,7 +157,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseHeaders = response.Headers; Assert.Equal( - new[] { "http://example.com" }, + new[] { "*" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); Assert.Equal( new[] { "true" }, @@ -179,16 +190,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal(HttpStatusCode.OK, response.StatusCode); var responseHeaders = response.Headers; Assert.Equal( - new[] { "http://example.com" }, + new[] { "*" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); Assert.Equal( new[] { "true" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); Assert.Equal( - new[] { "header1,header2" }, + new[] { "*" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); Assert.Equal( - new[] { "PUT" }, + new[] { "PUT,POST" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray()); var content = await response.Content.ReadAsStringAsync(); @@ -270,12 +281,43 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Empty(content); } - [Theory] - [InlineData("http://localhost/api/store/actionusingcontrollercorssettings")] - [InlineData("http://localhost/api/store/actionwithcorssettings")] - public async Task CorsFilter_RunsBeforeOtherAuthorizationFilters(string url) + [Fact] + public async Task CorsFilter_RunsBeforeOtherAuthorizationFilters_UsesPolicySpecifiedOnController() { // Arrange + var url = "http://localhost/api/store/actionusingcontrollercorssettings"; + var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), url); + + // Adding a custom header makes it a non-simple request. + request.Headers.Add(CorsConstants.Origin, "http://example.com"); + request.Headers.Add(CorsConstants.AccessControlRequestMethod, "GET"); + request.Headers.Add(CorsConstants.AccessControlRequestHeaders, "Custom"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var responseHeaders = response.Headers; + Assert.Equal( + new[] { "*" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowOrigin).ToArray()); + Assert.Equal( + new[] { "true" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); + Assert.Equal( + new[] { "*" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); + + var content = await response.Content.ReadAsStringAsync(); + Assert.Empty(content); + } + + [Fact] + public async Task CorsFilter_RunsBeforeOtherAuthorizationFilters_UsesPolicySpecifiedOnAction() + { + // Arrange + var url = "http://localhost/api/store/actionwithcorssettings"; var request = new HttpRequestMessage(new HttpMethod(CorsConstants.PreflightHttpMethod), url); // Adding a custom header makes it a non-simple request. @@ -296,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests new[] { "true" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); Assert.Equal( - new[] { "Custom" }, + new[] { "*" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); var content = await response.Content.ReadAsStringAsync(); From 8c58fdb7552288018becdccb9fcc018c597cd1f6 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 11 Oct 2018 11:32:03 -0700 Subject: [PATCH 292/316] Add Newtonsoft.Json.dll to Microsoft.Extensions.ApiDescription.Design package - also remove dotnet-getdocument.runtimeconfig.dev.json file --- NuGetPackageVerifier.json | 15 +++++++++++++++ ...rosoft.Extensions.ApiDescription.Design.csproj | 12 +++++++++++- ...rosoft.Extensions.ApiDescription.Design.nuspec | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/NuGetPackageVerifier.json b/NuGetPackageVerifier.json index a1e9573ee3..f551b83476 100644 --- a/NuGetPackageVerifier.json +++ b/NuGetPackageVerifier.json @@ -8,6 +8,21 @@ "Exclusions": { "BUILD_ITEMS_FRAMEWORK": { "*": "Package includes tool with different target frameworks." + }, + "SERVICING_ATTRIBUTE": { + "tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process" + }, + "WRONG_PUBLICKEYTOKEN": { + "tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process" + }, + "ASSEMBLY_INFORMATIONAL_VERSION_MISMATCH": { + "tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process" + }, + "ASSEMBLY_FILE_VERSION_MISMATCH": { + "tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process" + }, + "ASSEMBLY_VERSION_MISMATCH": { + "tools/Newtonsoft.Json.dll": "External assembly, not built as part of this process" } } } diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj index 164a547cc4..593a24fc70 100644 --- a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj @@ -37,7 +37,7 @@ - + $(AssemblySigningCertName) tools/dotnet-getdocument.dll $(AssemblySigningStrongName) @@ -57,9 +57,19 @@ tools/netcoreapp2.0/GetDocument.Insider.exe $(AssemblySigningStrongName) + + + tools/Newtonsoft.Json.dll" + $(AssemblySigning3rdPartyCertName)" + + + id=$(PackageId); diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec index 95ca2bcfd8..e4b8130e5b 100644 --- a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec +++ b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec @@ -22,7 +22,7 @@ - + From d3c8d171bd51897f14356016b48b9d8bf29dd4a2 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 11 Oct 2018 21:13:23 -0700 Subject: [PATCH 293/316] Fix builds that do real signing e.g. UniverseCoherence --- .../Microsoft.Extensions.ApiDescription.Design.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj index 593a24fc70..7809b19970 100644 --- a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj +++ b/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj @@ -60,7 +60,7 @@ tools/Newtonsoft.Json.dll" - $(AssemblySigning3rdPartyCertName)" + $(AssemblySigning3rdPartyCertName) From 164d14064cccbeeafe79713eba0b307bdfb1ad38 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Mon, 1 Oct 2018 15:05:56 -0700 Subject: [PATCH 294/316] Use casing for ProblemDetails that specified by RFC * Use JsonProperty.MemberName to specify lowercase casing for ProblemDetails properties - https://tools.ietf.org/html/rfc7807#section-3 * Use XML NS and lowercase for Xml elements specified by RFC - https://tools.ietf.org/html/rfc7807#appendix-A Fixes https://github.com/aspnet/Mvc/issues/8501 --- .../ProblemDetails.cs | 10 ++--- .../ValidationProblemDetails.cs | 2 + .../ProblemDetailsWrapper.cs | 24 +++++----- .../ValidationProblemDetailsWrapper.cs | 2 +- .../ProblemDetailsWrapperTest.cs | 18 ++++---- .../ValidationProblemDetailsWrapperTest.cs | 44 +++++++++---------- ...ontractSerializerFormattersWrappingTest.cs | 39 ++++++++++------ .../XmlSerializerFormattersWrappingTest.cs | 39 ++++++++++------ 8 files changed, 104 insertions(+), 74 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs index 44b816aa05..9d07004103 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs @@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc /// (e.g., using HTML [W3C.REC-html5-20141028]). When this member is not present, its value is assumed to be /// "about:blank". /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "type")] public string Type { get; set; } /// @@ -26,25 +26,25 @@ namespace Microsoft.AspNetCore.Mvc /// of the problem, except for purposes of localization(e.g., using proactive content negotiation; /// see[RFC7231], Section 3.4). /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "title")] public string Title { get; set; } /// /// The HTTP status code([RFC7231], Section 6) generated by the origin server for this occurrence of the problem. /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "status")] public int? Status { get; set; } /// /// A human-readable explanation specific to this occurrence of the problem. /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "detail")] public string Detail { get; set; } /// /// A URI reference that identifies the specific occurrence of the problem.It may or may not yield further information if dereferenced. /// - [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + [JsonProperty(NullValueHandling = NullValueHandling.Ignore, PropertyName = "instance")] public string Instance { get; set; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs b/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs index 2332367b95..da104c9a9b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json; namespace Microsoft.AspNetCore.Mvc { @@ -64,6 +65,7 @@ namespace Microsoft.AspNetCore.Mvc /// /// Gets or sets the validation errors associated with this instance of . /// + [JsonProperty(PropertyName = "errors")] public IDictionary Errors { get; } = new Dictionary(StringComparer.Ordinal); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs index 30775bd1b1..f7d2056806 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs @@ -12,9 +12,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml /// /// Wrapper class for to enable it to be serialized by the xml formatters. /// - [XmlRoot(nameof(ProblemDetails))] + [XmlRoot("problem", Namespace = Namespace)] public class ProblemDetailsWrapper : IXmlSerializable, IUnwrappable { + internal const string Namespace = "urn:ietf:rfc:7807"; + /// /// Key used to represent dictionary elements with empty keys /// @@ -83,25 +85,25 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml switch (name) { - case nameof(ProblemDetails.Detail): + case "detail": ProblemDetails.Detail = value; break; - case nameof(ProblemDetails.Instance): + case "instance": ProblemDetails.Instance = value; break; - case nameof(ProblemDetails.Status): + case "status": ProblemDetails.Status = string.IsNullOrEmpty(value) ? (int?)null : int.Parse(value, CultureInfo.InvariantCulture); break; - case nameof(ProblemDetails.Title): + case "title": ProblemDetails.Title = value; break; - case nameof(ProblemDetails.Type): + case "type": ProblemDetails.Type = value; break; @@ -122,20 +124,20 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml if (!string.IsNullOrEmpty(ProblemDetails.Detail)) { writer.WriteElementString( - XmlConvert.EncodeLocalName(nameof(ProblemDetails.Detail)), + XmlConvert.EncodeLocalName("detail"), ProblemDetails.Detail); } if (!string.IsNullOrEmpty(ProblemDetails.Instance)) { writer.WriteElementString( - XmlConvert.EncodeLocalName(nameof(ProblemDetails.Instance)), + XmlConvert.EncodeLocalName("instance"), ProblemDetails.Instance); } if (ProblemDetails.Status.HasValue) { - writer.WriteStartElement(XmlConvert.EncodeLocalName(nameof(ProblemDetails.Status))); + writer.WriteStartElement(XmlConvert.EncodeLocalName("status")); writer.WriteValue(ProblemDetails.Status.Value); writer.WriteEndElement(); } @@ -143,14 +145,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml if (!string.IsNullOrEmpty(ProblemDetails.Title)) { writer.WriteElementString( - XmlConvert.EncodeLocalName(nameof(ProblemDetails.Title)), + XmlConvert.EncodeLocalName("title"), ProblemDetails.Title); } if (!string.IsNullOrEmpty(ProblemDetails.Type)) { writer.WriteElementString( - XmlConvert.EncodeLocalName(nameof(ProblemDetails.Type)), + XmlConvert.EncodeLocalName("type"), ProblemDetails.Type); } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs index b8787ee0e0..a454fb5d0a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml /// /// Wrapper class for to enable it to be serialized by the xml formatters. /// - [XmlRoot(nameof(ValidationProblemDetails))] + [XmlRoot("problem", Namespace = "urn:ietf:rfc:7807")] public class ValidationProblemDetailsWrapper : ProblemDetailsWrapper, IUnwrappable { private static readonly string ErrorKey = "MVC-Errors"; diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs index b6760f0f15..88db6289c9 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs @@ -17,14 +17,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var xml = "" + - "" + - "Some title" + - "403" + - "Some instance" + + "" + + "Some title" + + "403" + + "Some instance" + "Test Value 1" + "<_x005B_key2_x005D_>Test Value 2" + "Test Value 3" + - ""; + ""; var serializer = new DataContractSerializer(typeof(ProblemDetailsWrapper)); // Act @@ -76,13 +76,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var wrapper = new ProblemDetailsWrapper(problemDetails); var outputStream = new MemoryStream(); var expectedContent = "" + - "" + - "Some detail" + - "Some title" + + "" + + "Some detail" + + "Some title" + "Test Value 1" + "<_x005B_Key2_x005D_>Test Value 2" + "Test Value 3" + - ""; + ""; // Act using (var xmlWriter = XmlWriter.Create(outputStream)) diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs index 50630a0082..53bae5afaf 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs @@ -17,10 +17,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var xml = "" + - "" + - "Some title" + - "400" + - "Some instance" + + "" + + "Some title" + + "400" + + "Some instance" + "Test Value 1" + "<_x005B_key2_x005D_>Test Value 2" + "" + @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "<_x005B_error2_x005D_>Test error 3" + "Test error 4" + "" + - ""; + ""; var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); // Act @@ -78,13 +78,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var xml = "" + - "" + - "Some title" + - "400" + - "Some instance" + + "" + + "Some title" + + "400" + + "Some instance" + "Test Value 1" + "<_x005B_key2_x005D_>Test Value 2" + - ""; + ""; var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); // Act @@ -118,11 +118,11 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { // Arrange var xml = "" + - "" + - "Some title" + - "400" + + "" + + "Some title" + + "400" + "" + - ""; + ""; var serializer = new DataContractSerializer(typeof(ValidationProblemDetailsWrapper)); // Act @@ -160,9 +160,9 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var wrapper = new ValidationProblemDetailsWrapper(problemDetails); var outputStream = new MemoryStream(); var expectedContent = "" + - "" + - "Some detail" + - "Some title" + + "" + + "Some detail" + + "Some title" + "Test Value 1" + "<_x005B_Key2_x005D_>Test Value 2" + "" + @@ -170,7 +170,7 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml "<_x005B_error2_x005D_>Test error 3" + "Test error 4" + "" + - ""; + ""; // Act using (var xmlWriter = XmlWriter.Create(outputStream)) @@ -203,12 +203,12 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml var wrapper = new ValidationProblemDetailsWrapper(problemDetails); var outputStream = new MemoryStream(); var expectedContent = "" + - "" + - "Some detail" + - "Some title" + + "" + + "Some detail" + + "Some title" + "Test Value 1" + "<_x005B_Key2_x005D_>Test Value 2" + - ""; + ""; // Act using (var xmlWriter = XmlWriter.Create(outputStream)) diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs index 2e34fbbe27..d85de4c3c7 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs @@ -216,12 +216,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange using (new ActivityReplacer()) { - var expected = "" + - "404" + - "Not Found" + - "https://tools.ietf.org/html/rfc7231#section-6.5.4" + + var expected = "" + + "404" + + "Not Found" + + "https://tools.ietf.org/html/rfc7231#section-6.5.4" + $"{Activity.Current.Id}" + - ""; + ""; // Act var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningClientErrorStatusCodeResult"); @@ -237,8 +237,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ProblemDetails_WithExtensionMembers_IsSerialized() { // Arrange - var expected = @"instance404title -correlationAccount1 Account2"; + var expected = "" + + "instance" + + "404" + + "title" + + "correlation" + + "Account1 Account2" + + ""; // Act var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningProblemDetails"); @@ -255,14 +260,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange using (new ActivityReplacer()) { - var expected = "" + - "400" + - "One or more validation errors occurred." + + var expected = "" + + "400" + + "One or more validation errors occurred." + $"{Activity.Current.Id}" + "" + "The State field is required." + "" + - ""; + ""; // Act var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationProblem"); @@ -278,8 +283,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ValidationProblemDetails_WithExtensionMembers_IsSerialized() { // Arrange - var expected = @"some detail400One or more validation errors occurred. -some typecorrelationErrorValue"; + var expected = "" + + "some detail" + + "400" + + "One or more validation errors occurred." + + "some type" + + "correlation" + + "" + + "ErrorValue" + + "" + + ""; // Act var response = await Client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationDetailsWithMetadata"); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs index b8b41c5f0a..33c76b98e8 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs @@ -191,12 +191,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange using (new ActivityReplacer()) { - var expected = "" + - "404" + - "Not Found" + - "https://tools.ietf.org/html/rfc7231#section-6.5.4" + + var expected = "" + + "404" + + "Not Found" + + "https://tools.ietf.org/html/rfc7231#section-6.5.4" + $"{Activity.Current.Id}" + - ""; + ""; // Act var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningClientErrorStatusCodeResult"); @@ -212,8 +212,13 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ProblemDetails_WithExtensionMembers_IsSerialized() { // Arrange - var expected = @"instance404title -correlationAccount1 Account2"; + var expected = "" + + "instance" + + "404" + + "title" + + "correlation" + + "Account1 Account2" + + ""; // Act var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningProblemDetails"); @@ -230,14 +235,14 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests // Arrange using (new ActivityReplacer()) { - var expected = "" + - "400" + - "One or more validation errors occurred." + + var expected = "" + + "400" + + "One or more validation errors occurred." + $"{Activity.Current.Id}" + "" + "The State field is required." + "" + - ""; + ""; // Act var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationProblem"); @@ -253,8 +258,16 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests public async Task ValidationProblemDetails_WithExtensionMembers_IsSerialized() { // Arrange - var expected = @"some detail400One or more validation errors occurred. -some typecorrelationErrorValue"; + var expected = "" + + "some detail" + + "400" + + "One or more validation errors occurred." + + "some type" + + "correlation" + + "" + + "ErrorValue" + + "" + + ""; // Act var response = await Client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationDetailsWithMetadata"); From a40c1f2d026b96c791e4cf88432555b92da008cf Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 11 Oct 2018 10:46:29 -0700 Subject: [PATCH 295/316] Use compat flag to drive XML ProblemDetails formatting --- .../MvcXmlMvcBuilderExtensions.cs | 80 +++++- .../MvcXmlMvcCoreBuilderExtensions.cs | 81 ++++++- .../MvcXmlOptions.cs | 68 ++++++ ...XmlOptionsConfigureCompatibilityOptions.cs | 36 +++ .../ProblemDetails21Wrapper.cs | 179 ++++++++++++++ .../ProblemDetailsWrapperProviderFactory.cs | 65 +++++ .../ValidationProblemDetails21Wrapper.cs | 127 ++++++++++ .../WrapperProviderFactoriesExtensions.cs | 19 -- .../WrapperProviderFactory.cs | 50 ---- ...XmlDataContractSerializerInputFormatter.cs | 5 +- ...lDataContractSerializerMvcOptionsSetup.cs} | 33 ++- ...mlDataContractSerializerOutputFormatter.cs | 5 +- .../XmlSerializerInputFormatter.cs | 5 +- ...tup.cs => XmlSerializerMvcOptionsSetup.cs} | 32 ++- .../XmlSerializerOutputFormatter.cs | 5 +- ...taContractSerializerMvcOptionsSetupTest.cs | 42 ---- .../MvcXmlSerializerMvcOptionsSetupTest.cs | 42 ---- .../ProblemDetails21WrapperTest.cs | 102 ++++++++ ...roblemDetailsWrapperProviderFactoryTest.cs | 119 +++++++++ .../ProblemDetailsWrapperTest.cs | 1 - .../ValidationProblemDetails21WrapperTest.cs | 228 ++++++++++++++++++ .../WrapperProviderFactoryExtensionsTest.cs | 34 --- .../WrapperProviderFactoryTest.cs | 63 ----- ...taContractSerializerMvcOptionsSetupTest.cs | 71 ++++++ .../XmlSerializerMvcOptionsSetupTest.cs | 71 ++++++ ...ontractSerializerFormattersWrappingTest.cs | 61 ++++- .../XmlSerializerFormattersWrappingTest.cs | 61 ++++- .../TestMvcOptions.cs | 4 - .../CompatibilitySwitchIntegrationTest.cs | 29 ++- .../XmlDataContractApiController.cs | 8 +- .../Controllers/XmlSerializedApiController.cs | 8 +- test/WebSites/XmlFormattersWebSite/Startup.cs | 76 ++++-- .../StartupWith21Compat.cs | 13 + 33 files changed, 1505 insertions(+), 318 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs delete mode 100644 src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs rename src/Microsoft.AspNetCore.Mvc.Formatters.Xml/{Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs => XmlDataContractSerializerMvcOptionsSetup.cs} (57%) rename src/Microsoft.AspNetCore.Mvc.Formatters.Xml/{Internal/MvcXmlSerializerMvcOptionsSetup.cs => XmlSerializerMvcOptionsSetup.cs} (51%) delete mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs delete mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs create mode 100644 test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs create mode 100644 test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs index 4210fbe0bc..17bce01d5e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs @@ -3,7 +3,7 @@ using System; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -14,6 +14,29 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class MvcXmlMvcBuilderExtensions { + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + public static IMvcBuilder AddXmlOptions( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.Services.Configure(setupAction); + return builder; + } + /// /// Adds the XML DataContractSerializer formatters to MVC. /// @@ -30,6 +53,31 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + /// + /// Adds the XML DataContractSerializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// The . + public static IMvcBuilder AddXmlDataContractSerializerFormatters( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + AddXmlDataContractSerializerFormatterServices(builder.Services); + builder.Services.Configure(setupAction); + return builder; + } + /// /// Adds the XML Serializer formatters to MVC. /// @@ -46,18 +94,44 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + /// + /// Adds the XML Serializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// The . + public static IMvcBuilder AddXmlSerializerFormatters( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlSerializerFormatterServices(builder.Services); + builder.Services.Configure(setupAction); + return builder; + } + // Internal for testing. internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services) { services.TryAddEnumerable( - ServiceDescriptor.Transient, MvcXmlDataContractSerializerMvcOptionsSetup>()); + ServiceDescriptor.Transient, XmlDataContractSerializerMvcOptionsSetup>()); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlOptionsConfigureCompatibilityOptions>()); } // Internal for testing. internal static void AddXmlSerializerFormatterServices(IServiceCollection services) { services.TryAddEnumerable( - ServiceDescriptor.Transient, MvcXmlSerializerMvcOptionsSetup>()); + ServiceDescriptor.Transient, XmlSerializerMvcOptionsSetup>()); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlOptionsConfigureCompatibilityOptions>()); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs index 5e14f41647..9608d3fc18 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs @@ -3,7 +3,7 @@ using System; using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; @@ -14,6 +14,30 @@ namespace Microsoft.Extensions.DependencyInjection /// public static class MvcXmlMvcCoreBuilderExtensions { + /// + /// Adds configuration of for the application. + /// + /// The . + /// The which need to be configured. + /// The . + public static IMvcCoreBuilder AddXmlOptions( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.Services.Configure(setupAction); + return builder; + } + /// /// Adds the XML DataContractSerializer formatters to MVC. /// @@ -30,6 +54,31 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + /// + /// Adds the XML DataContractSerializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// The . + public static IMvcCoreBuilder AddXmlDataContractSerializerFormatters( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + AddXmlDataContractSerializerFormatterServices(builder.Services); + builder.Services.Configure(setupAction); + return builder; + } + /// /// Adds the XML Serializer formatters to MVC. /// @@ -46,18 +95,44 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + /// + /// Adds the XML Serializer formatters to MVC. + /// + /// The . + /// The which need to be configured. + /// /// The . + public static IMvcCoreBuilder AddXmlSerializerFormatters( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + AddXmlSerializerFormatterServices(builder.Services); + builder.Services.Configure(setupAction); + return builder; + } + // Internal for testing. internal static void AddXmlDataContractSerializerFormatterServices(IServiceCollection services) { services.TryAddEnumerable( - ServiceDescriptor.Transient, MvcXmlDataContractSerializerMvcOptionsSetup>()); + ServiceDescriptor.Transient, XmlDataContractSerializerMvcOptionsSetup>()); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlOptionsConfigureCompatibilityOptions>()); } // Internal for testing. internal static void AddXmlSerializerFormatterServices(IServiceCollection services) { services.TryAddEnumerable( - ServiceDescriptor.Transient, MvcXmlSerializerMvcOptionsSetup>()); + ServiceDescriptor.Transient, XmlSerializerMvcOptionsSetup>()); + + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcXmlOptionsConfigureCompatibilityOptions>()); } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs new file mode 100644 index 0000000000..d8ada74590 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs @@ -0,0 +1,68 @@ +// 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; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Provides configuration for XML formatters. + /// + public class MvcXmlOptions : IEnumerable + { + private readonly CompatibilitySwitch _allowRfc7807CompliantProblemDetailsFormat; + private readonly IReadOnlyList _switches; + + /// + /// Creates a new instance of . + /// + public MvcXmlOptions() + { + _allowRfc7807CompliantProblemDetailsFormat = new CompatibilitySwitch(nameof(AllowRfc7807CompliantProblemDetailsFormat)); + + _switches = new ICompatibilitySwitch[] + { + _allowRfc7807CompliantProblemDetailsFormat, + }; + } + + /// + /// Gets or sets a value inidicating whether and + /// are serialized in a format compliant with the RFC 7807 specification (https://tools.ietf.org/html/rfc7807). + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take + /// precedence over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to or + /// lower then this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// higher then this setting will have the value unless explicitly configured. + /// + /// + public bool AllowRfc7807CompliantProblemDetailsFormat + { + get => _allowRfc7807CompliantProblemDetailsFormat.Value; + set => _allowRfc7807CompliantProblemDetailsFormat.Value = value; + } + + public IEnumerator GetEnumerator() => _switches.GetEnumerator(); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs new file mode 100644 index 0000000000..c5d1d3e340 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs @@ -0,0 +1,36 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc +{ + internal sealed class MvcXmlOptionsConfigureCompatibilityOptions : ConfigureCompatibilityOptions + { + public MvcXmlOptionsConfigureCompatibilityOptions( + ILoggerFactory loggerFactory, + IOptions compatibilityOptions) + : base(loggerFactory, compatibilityOptions) + { + } + + protected override IReadOnlyDictionary DefaultValues + { + get + { + var values = new Dictionary(); + + if (Version >= CompatibilityVersion.Version_2_2) + { + values[nameof(MvcXmlOptions.AllowRfc7807CompliantProblemDetailsFormat)] = true; + } + + return values; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs new file mode 100644 index 0000000000..9a4dcf0bd7 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs @@ -0,0 +1,179 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Globalization; +using System.Xml; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Wrapper class for to enable it to be serialized by the xml formatters. + /// + [XmlRoot(nameof(ProblemDetails))] + [Obsolete("This type is deprecated and will be removed in a future version")] + public class ProblemDetails21Wrapper : IXmlSerializable, IUnwrappable + { + protected static readonly string EmptyKey = SerializableErrorWrapper.EmptyKey; + + public ProblemDetails21Wrapper() + : this(new ProblemDetails()) + { + } + + public ProblemDetails21Wrapper(ProblemDetails problemDetails) + { + ProblemDetails = problemDetails; + } + + internal ProblemDetails ProblemDetails { get; } + + /// + public XmlSchema GetSchema() => null; + + /// + public virtual void ReadXml(XmlReader reader) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + if (reader.IsEmptyElement) + { + reader.Read(); + return; + } + + reader.ReadStartElement(); + while (reader.NodeType != XmlNodeType.EndElement) + { + var key = XmlConvert.DecodeName(reader.LocalName); + ReadValue(reader, key); + + reader.MoveToContent(); + } + + reader.ReadEndElement(); + } + + /// + /// Reads the value for the specified from the . + /// + /// The . + /// The name of the node. + protected virtual void ReadValue(XmlReader reader, string name) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + var value = reader.ReadInnerXml(); + + switch (name) + { + case "Detail": + ProblemDetails.Detail = value; + break; + + case "Instance": + ProblemDetails.Instance = value; + break; + + case "Status": + ProblemDetails.Status = string.IsNullOrEmpty(value) ? + (int?)null : + int.Parse(value, CultureInfo.InvariantCulture); + break; + + case "Title": + ProblemDetails.Title = value; + break; + + case "Type": + ProblemDetails.Type = value; + break; + + default: + if (string.Equals(name, EmptyKey, StringComparison.Ordinal)) + { + name = string.Empty; + } + + ProblemDetails.Extensions.Add(name, value); + break; + } + } + + /// + public virtual void WriteXml(XmlWriter writer) + { + if (!string.IsNullOrEmpty(ProblemDetails.Detail)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName("Detail"), + ProblemDetails.Detail); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Instance)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName("Instance"), + ProblemDetails.Instance); + } + + if (ProblemDetails.Status.HasValue) + { + writer.WriteStartElement(XmlConvert.EncodeLocalName("Status")); + writer.WriteValue(ProblemDetails.Status.Value); + writer.WriteEndElement(); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Title)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName("Title"), + ProblemDetails.Title); + } + + if (!string.IsNullOrEmpty(ProblemDetails.Type)) + { + writer.WriteElementString( + XmlConvert.EncodeLocalName("Type"), + ProblemDetails.Type); + } + + foreach (var keyValuePair in ProblemDetails.Extensions) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + + if (string.IsNullOrEmpty(key)) + { + key = EmptyKey; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); + if (value != null) + { + writer.WriteValue(value); + } + + writer.WriteEndElement(); + } + } + + object IUnwrappable.Unwrap(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + return ProblemDetails; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs new file mode 100644 index 0000000000..9b93b86c6b --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs @@ -0,0 +1,65 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + internal class ProblemDetailsWrapperProviderFactory : IWrapperProviderFactory + { + private readonly MvcXmlOptions _options; + + public ProblemDetailsWrapperProviderFactory(MvcXmlOptions options) + { + _options = options; + } + + public IWrapperProvider GetProvider(WrapperProviderContext context) + { + if (context.DeclaredType == typeof(ProblemDetails)) + { + if (_options.AllowRfc7807CompliantProblemDetailsFormat) + { + return new WrapperProvider(typeof(ProblemDetailsWrapper), p => new ProblemDetailsWrapper((ProblemDetails)p)); + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + return new WrapperProvider(typeof(ProblemDetails21Wrapper), p => new ProblemDetails21Wrapper((ProblemDetails)p)); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + if (context.DeclaredType == typeof(ValidationProblemDetails)) + { + if (_options.AllowRfc7807CompliantProblemDetailsFormat) + { + return new WrapperProvider(typeof(ValidationProblemDetailsWrapper), p => new ValidationProblemDetailsWrapper((ValidationProblemDetails)p)); + } + else + { +#pragma warning disable CS0618 // Type or member is obsolete + return new WrapperProvider(typeof(ValidationProblemDetails21Wrapper), p => new ValidationProblemDetails21Wrapper((ValidationProblemDetails)p)); +#pragma warning restore CS0618 // Type or member is obsolete + } + } + + return null; + } + + private class WrapperProvider : IWrapperProvider + { + public WrapperProvider(Type wrappingType, Func wrapDelegate) + { + WrappingType = wrappingType; + WrapDelegate = wrapDelegate; + } + + public Type WrappingType { get; } + + public Func WrapDelegate { get; } + + public object Wrap(object original) => WrapDelegate(original); + } + } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs new file mode 100644 index 0000000000..e138c0f3d1 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs @@ -0,0 +1,127 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Xml; +using System.Xml.Serialization; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + /// + /// Wrapper class for to enable it to be serialized by the xml formatters. + /// + [XmlRoot(nameof(ValidationProblemDetails))] + [Obsolete("This type is deprecated and will be removed in a future version")] + public class ValidationProblemDetails21Wrapper : ProblemDetails21Wrapper, IUnwrappable + { + private static readonly string ErrorKey = "MVC-Errors"; + + /// + /// Initializes a new instance of . + /// + public ValidationProblemDetails21Wrapper() + : this(new ValidationProblemDetails()) + { + } + + /// + /// Initializes a new instance of for the specified + /// . + /// + /// The . + public ValidationProblemDetails21Wrapper(ValidationProblemDetails problemDetails) + : base(problemDetails) + { + ProblemDetails = problemDetails; + } + + internal new ValidationProblemDetails ProblemDetails { get; } + + /// + protected override void ReadValue(XmlReader reader, string name) + { + if (reader == null) + { + throw new ArgumentNullException(nameof(reader)); + } + + if (string.Equals(name, ErrorKey, StringComparison.Ordinal)) + { + reader.Read(); + ReadErrorProperty(reader); + } + else + { + base.ReadValue(reader, name); + } + } + + private void ReadErrorProperty(XmlReader reader) + { + if (reader.IsEmptyElement) + { + return; + } + + while (reader.NodeType != XmlNodeType.EndElement) + { + var key = XmlConvert.DecodeName(reader.LocalName); + var value = reader.ReadInnerXml(); + if (string.Equals(EmptyKey, key, StringComparison.Ordinal)) + { + key = string.Empty; + } + + ProblemDetails.Errors.Add(key, new[] { value }); + reader.MoveToContent(); + } + } + + /// + public override void WriteXml(XmlWriter writer) + { + if (writer == null) + { + throw new ArgumentNullException(nameof(writer)); + } + + base.WriteXml(writer); + + if (ProblemDetails.Errors.Count == 0) + { + return; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(ErrorKey)); + + foreach (var keyValuePair in ProblemDetails.Errors) + { + var key = keyValuePair.Key; + var value = keyValuePair.Value; + if (string.IsNullOrEmpty(key)) + { + key = EmptyKey; + } + + writer.WriteStartElement(XmlConvert.EncodeLocalName(key)); + if (value != null) + { + writer.WriteValue(value); + } + + writer.WriteEndElement(); + } + writer.WriteEndElement(); + } + + object IUnwrappable.Unwrap(Type declaredType) + { + if (declaredType == null) + { + throw new ArgumentNullException(nameof(declaredType)); + } + + return ProblemDetails; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs index 6ff62a8ec0..1ccea1645d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs @@ -44,24 +44,5 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml return null; } - - internal static IList GetDefaultProviderFactories() - { - var wrapperProviderFactories = new List(); - - wrapperProviderFactories.Add(new SerializableErrorWrapperProviderFactory()); - - wrapperProviderFactories.Add(new WrapperProviderFactory( - typeof(ProblemDetails), - typeof(ProblemDetailsWrapper), - value => new ProblemDetailsWrapper((ProblemDetails)value))); - - wrapperProviderFactories.Add(new WrapperProviderFactory( - typeof(ValidationProblemDetails), - typeof(ValidationProblemDetailsWrapper), - value => new ValidationProblemDetailsWrapper((ValidationProblemDetails)value))); - - return wrapperProviderFactories; - } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs deleted file mode 100644 index 3f7c4a48af..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactory.cs +++ /dev/null @@ -1,50 +0,0 @@ -// 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; - -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml -{ - internal class WrapperProviderFactory : IWrapperProviderFactory - { - public WrapperProviderFactory(Type declaredType, Type wrappingType, Func wrapper) - { - DeclaredType = declaredType; - WrappingType = wrappingType; - Wrapper = wrapper; - } - - public Type DeclaredType { get; } - - public Type WrappingType { get; } - - public Func Wrapper { get; } - - public IWrapperProvider GetProvider(WrapperProviderContext context) - { - if (context.DeclaredType == DeclaredType) - { - return new WrapperProvider(this); - } - - return null; - } - - private class WrapperProvider : IWrapperProvider - { - private readonly WrapperProviderFactory _wrapperFactory; - - public WrapperProvider(WrapperProviderFactory wrapperFactory) - { - _wrapperFactory = wrapperFactory; - } - - public Type WrappingType => _wrapperFactory.WrappingType; - - public object Wrap(object original) - { - return _wrapperFactory.Wrapper(original); - } - } - } -} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs index b5d6d74a22..f0954c36a4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs @@ -46,7 +46,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters _serializerSettings = new DataContractSerializerSettings(); - WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); + WrapperProviderFactories = new List + { + new SerializableErrorWrapperProviderFactory(), + }; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs similarity index 57% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs rename to src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs index c1cd77f685..eba6b9d5fd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlDataContractSerializerMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs @@ -2,34 +2,36 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Xml; +using System.Xml.Linq; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { /// /// A implementation which will add the /// data contract serializer formatters to . /// - public class MvcXmlDataContractSerializerMvcOptionsSetup : IConfigureOptions + internal sealed class XmlDataContractSerializerMvcOptionsSetup : IConfigureOptions { + private readonly MvcXmlOptions _xmlOptions; private readonly ILoggerFactory _loggerFactory; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// + /// . /// The . - public MvcXmlDataContractSerializerMvcOptionsSetup(ILoggerFactory loggerFactory) + public XmlDataContractSerializerMvcOptionsSetup( + IOptions xmlOptions, + ILoggerFactory loggerFactory) { - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - - _loggerFactory = loggerFactory; + _xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions)); + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -40,8 +42,13 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal { options.ModelMetadataDetailsProviders.Add(new DataMemberRequiredBindingMetadataProvider()); - options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter(_loggerFactory)); - options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(options)); + var inputFormatter = new XmlDataContractSerializerInputFormatter(options); + inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions)); + options.InputFormatters.Add(inputFormatter); + + var outputFormatter = new XmlDataContractSerializerOutputFormatter(_loggerFactory); + outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions)); + options.OutputFormatters.Add(outputFormatter); // Do not override any user mapping var key = "xml"; diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs index 6a312d903d..9b89042636 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs @@ -76,7 +76,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters _serializerSettings = new DataContractSerializerSettings(); - WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); + WrapperProviderFactories = new List() + { + new SerializableErrorWrapperProviderFactory(), + }; WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); _logger = loggerFactory?.CreateLogger(GetType()); diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs index 2c1ea40bbd..4d530a015b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs @@ -43,7 +43,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters SupportedMediaTypes.Add(MediaTypeHeaderValues.TextXml); SupportedMediaTypes.Add(MediaTypeHeaderValues.ApplicationAnyXmlSyntax); - WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); + WrapperProviderFactories = new List + { + new SerializableErrorWrapperProviderFactory(), + }; } /// diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs similarity index 51% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs rename to src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs index 6c7546e332..1a57d167ce 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MvcXmlSerializerMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs @@ -2,32 +2,32 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; -using Microsoft.Net.Http.Headers; -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml { /// /// A implementation which will add the /// XML serializer formatters to . /// - public class MvcXmlSerializerMvcOptionsSetup : IConfigureOptions + internal sealed class XmlSerializerMvcOptionsSetup : IConfigureOptions { + private readonly MvcXmlOptions _xmlOptions; private readonly ILoggerFactory _loggerFactory; /// - /// Initializes a new instance of . + /// Initializes a new instance of . /// + /// . /// The . - public MvcXmlSerializerMvcOptionsSetup(ILoggerFactory loggerFactory) + public XmlSerializerMvcOptionsSetup( + IOptions xmlOptions, + ILoggerFactory loggerFactory) { - if (loggerFactory == null) - { - throw new ArgumentNullException(nameof(loggerFactory)); - } - - _loggerFactory = loggerFactory; + _xmlOptions = xmlOptions?.Value ?? throw new ArgumentNullException(nameof(xmlOptions)); + _loggerFactory = loggerFactory ?? throw new ArgumentNullException(nameof(loggerFactory)); } /// @@ -46,8 +46,14 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal MediaTypeHeaderValues.ApplicationXml); } - options.OutputFormatters.Add(new XmlSerializerOutputFormatter(_loggerFactory)); - options.InputFormatters.Add(new XmlSerializerInputFormatter(options)); + var inputFormatter = new XmlSerializerInputFormatter(options); + inputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions)); + options.InputFormatters.Add(inputFormatter); + + var outputFormatter = new XmlSerializerOutputFormatter(_loggerFactory); + outputFormatter.WrapperProviderFactories.Add(new ProblemDetailsWrapperProviderFactory(_xmlOptions)); + options.OutputFormatters.Add(outputFormatter); + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs index e24b2c9d20..c289972ea4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs @@ -73,7 +73,10 @@ namespace Microsoft.AspNetCore.Mvc.Formatters WriterSettings = writerSettings; - WrapperProviderFactories = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); + WrapperProviderFactories = new List + { + new SerializableErrorWrapperProviderFactory(), + }; WrapperProviderFactories.Add(new EnumerableWrapperProviderFactory(WrapperProviderFactories)); _logger = loggerFactory?.CreateLogger(GetType()); diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs deleted file mode 100644 index 5219f0b97a..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlDataContractSerializerMvcOptionsSetupTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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.Extensions.Logging.Abstractions; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal -{ - public class MvcXmlDataContractSerializerMvcOptionsSetupTest - { - [Fact] - public void AddsFormatterMapping() - { - // Arrange - var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance); - var options = new MvcOptions(); - - // Act - optionsSetup.Configure(options); - - // Assert - var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); - Assert.Equal("application/xml", mappedContentType); - } - - [Fact] - public void DoesNotOverrideExistingMapping() - { - // Arrange - var optionsSetup = new MvcXmlDataContractSerializerMvcOptionsSetup(NullLoggerFactory.Instance); - var options = new MvcOptions(); - options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml"); - - // Act - optionsSetup.Configure(options); - - // Assert - var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); - Assert.Equal("text/xml", mappedContentType); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs deleted file mode 100644 index 0ff2072a4c..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/MvcXmlSerializerMvcOptionsSetupTest.cs +++ /dev/null @@ -1,42 +0,0 @@ -// 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.Extensions.Logging.Abstractions; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml.Internal -{ - public class MvcXmlSerializerMvcOptionsSetupTest - { - [Fact] - public void AddsFormatterMapping() - { - // Arrange - var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance); - var options = new MvcOptions(); - - // Act - optionsSetup.Configure(options); - - // Assert - var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); - Assert.Equal("application/xml", mappedContentType); - } - - [Fact] - public void DoesNotOverrideExistingMapping() - { - // Arrange - var optionsSetup = new MvcXmlSerializerMvcOptionsSetup(NullLoggerFactory.Instance); - var options = new MvcOptions(); - options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml"); - - // Act - optionsSetup.Configure(options); - - // Assert - var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); - Assert.Equal("text/xml", mappedContentType); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs new file mode 100644 index 0000000000..933da23704 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs @@ -0,0 +1,102 @@ +// 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 System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ +#pragma warning disable CS0618 // Type or member is obsolete + public class ProblemDetails21WrapperTest + { + [Fact] + public void ReadXml_ReadsProblemDetailsXml() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "403" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + "Test Value 3" + + ""; + var serializer = new DataContractSerializer(typeof(ProblemDetails21Wrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(403, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal("Test Value 3", kvp.Value); + }, + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }); + } + + [Fact] + public void WriteXml_WritesValidXml() + { + // Arrange + var problemDetails = new ProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2", + [""] = "Test Value 3", + }, + }; + + var wrapper = new ProblemDetails21Wrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + "Test Value 3" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + } +#pragma warning restore CS0618 // Type or member is obsolete + +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs new file mode 100644 index 0000000000..21a62006b5 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs @@ -0,0 +1,119 @@ +// 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 Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class ProblemDetailsWrapperProviderFactoryTest + { + [Fact] + public void GetProvider_ReturnsNull_IfTypeDoesNotMatch() + { + // Arrange + var xmlOptions = new MvcXmlOptions(); + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var context = new WrapperProviderContext(typeof(SerializableError), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + Assert.Null(provider); + } + + [Fact] + public void GetProvider_ReturnsWrapper_ForProblemDetails() + { + // Arrange + var xmlOptions = new MvcXmlOptions { AllowRfc7807CompliantProblemDetailsFormat = true }; + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var instance = new ProblemDetails(); + var context = new WrapperProviderContext(instance.GetType(), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + var result = provider.Wrap(instance); + var wrapper = Assert.IsType(result); + Assert.Same(instance, wrapper.ProblemDetails); + } + + [Fact] + public void GetProvider_Returns21CompatibleWrapper_ForProblemDetails() + { + // Arrange + var xmlOptions = new MvcXmlOptions(); + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var instance = new ProblemDetails(); + var context = new WrapperProviderContext(instance.GetType(), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + var result = provider.Wrap(instance); +#pragma warning disable CS0618 // Type or member is obsolete + var wrapper = Assert.IsType(result); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.Same(instance, wrapper.ProblemDetails); + } + + [Fact] + public void GetProvider_ReturnsWrapper_ForValidationProblemDetails() + { + // Arrange + var xmlOptions = new MvcXmlOptions { AllowRfc7807CompliantProblemDetailsFormat = true }; + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var instance = new ValidationProblemDetails(); + var context = new WrapperProviderContext(instance.GetType(), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + var result = provider.Wrap(instance); + var wrapper = Assert.IsType(result); + Assert.Same(instance, wrapper.ProblemDetails); + } + + [Fact] + public void GetProvider_Returns21CompatibleWrapper_ForValidationProblemDetails() + { + // Arrange + var xmlOptions = new MvcXmlOptions(); + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var instance = new ValidationProblemDetails(); + var context = new WrapperProviderContext(instance.GetType(), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + var result = provider.Wrap(instance); +#pragma warning disable CS0618 // Type or member is obsolete + var wrapper = Assert.IsType(result); +#pragma warning restore CS0618 // Type or member is obsolete + Assert.Same(instance, wrapper.ProblemDetails); + } + + [Fact] + public void GetProvider_ReturnsNull_ForCustomProblemDetails() + { + // Arrange + var xmlOptions = new MvcXmlOptions(); + var providerFactory = new ProblemDetailsWrapperProviderFactory(xmlOptions); + var instance = new CustomProblemDetails(); + var context = new WrapperProviderContext(instance.GetType(), isSerialization: true); + + // Act + var provider = providerFactory.GetProvider(context); + + // Assert + Assert.Null(provider); + } + + private class CustomProblemDetails : ProblemDetails { } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs index 88db6289c9..055679f266 100644 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs @@ -56,7 +56,6 @@ namespace Microsoft.AspNetCore.Mvc.Formatters.Xml }); } - [Fact] public void WriteXml_WritesValidXml() { diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs new file mode 100644 index 0000000000..49b41afaf4 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs @@ -0,0 +1,228 @@ +// 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 System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Xml; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ +#pragma warning disable CS0618 // Type or member is obsolete + public class ValidationProblemDetails21WrapperTest + { + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + "" + + "Test error 1 Test error 2" + + "<_x005B_error2_x005D_>Test error 3" + + "Test error 4" + + "" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(400, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }); + + Assert.Collection( + problemDetails.Errors.OrderBy(kvp => kvp.Key), + kvp => + { + Assert.Empty(kvp.Key); + Assert.Equal(new[] { "Test error 4" }, kvp.Value); + }, + kvp => + { + Assert.Equal("[error2]", kvp.Key); + Assert.Equal(new[] { "Test error 3" }, kvp.Value); + }, + kvp => + { + Assert.Equal("error1", kvp.Key); + Assert.Equal(new[] { "Test error 1 Test error 2" }, kvp.Value); + }); + } + + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml_WithNoErrors() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "Some instance" + + "Test Value 1" + + "<_x005B_key2_x005D_>Test Value 2" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal("Some instance", problemDetails.Instance); + Assert.Equal(400, problemDetails.Status); + + Assert.Collection( + problemDetails.Extensions, + kvp => + { + Assert.Equal("key1", kvp.Key); + Assert.Equal("Test Value 1", kvp.Value); + }, + kvp => + { + Assert.Equal("[key2]", kvp.Key); + Assert.Equal("Test Value 2", kvp.Value); + }); + + Assert.Empty(problemDetails.Errors); + } + + [Fact] + public void ReadXml_ReadsValidationProblemDetailsXml_WithEmptyErrorsElement() + { + // Arrange + var xml = "" + + "" + + "Some title" + + "400" + + "" + + ""; + var serializer = new DataContractSerializer(typeof(ValidationProblemDetails21Wrapper)); + + // Act + var value = serializer.ReadObject( + new MemoryStream(Encoding.UTF8.GetBytes(xml))); + + // Assert + var problemDetails = Assert.IsType(value).ProblemDetails; + Assert.Equal("Some title", problemDetails.Title); + Assert.Equal(400, problemDetails.Status); + Assert.Empty(problemDetails.Errors); + } + + [Fact] + public void WriteXml_WritesValidXml() + { + // Arrange + var problemDetails = new ValidationProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2" + }, + Errors = + { + { "error1", new[] {"Test error 1", "Test error 2" } }, + { "[error2]", new[] {"Test error 3" } }, + { "", new[] { "Test error 4" } }, + } + }; + + var wrapper = new ValidationProblemDetails21Wrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + "" + + "Test error 1 Test error 2" + + "<_x005B_error2_x005D_>Test error 3" + + "Test error 4" + + "" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + + [Fact] + public void WriteXml_WithNoValidationErrors() + { + // Arrange + var problemDetails = new ValidationProblemDetails + { + Title = "Some title", + Detail = "Some detail", + Extensions = + { + ["key1"] = "Test Value 1", + ["[Key2]"] = "Test Value 2" + }, + }; + + var wrapper = new ValidationProblemDetails21Wrapper(problemDetails); + var outputStream = new MemoryStream(); + var expectedContent = "" + + "" + + "Some detail" + + "Some title" + + "Test Value 1" + + "<_x005B_Key2_x005D_>Test Value 2" + + ""; + + // Act + using (var xmlWriter = XmlWriter.Create(outputStream)) + { + var dataContractSerializer = new DataContractSerializer(wrapper.GetType()); + dataContractSerializer.WriteObject(xmlWriter, wrapper); + } + outputStream.Position = 0; + var res = new StreamReader(outputStream, Encoding.UTF8).ReadToEnd(); + + // Assert + Assert.Equal(expectedContent, res); + } + } +#pragma warning restore CS0618 // Type or member is obsolete +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs deleted file mode 100644 index ca981f679a..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryExtensionsTest.cs +++ /dev/null @@ -1,34 +0,0 @@ -// 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 Xunit; - -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml -{ - public class WrapperProviderFactoryExtensionsTest - { - [Fact] - public void GetDefaultProviderFactories_GetsFactoriesUsedByInputAndOutputFormatters() - { - // Act - var factoryProviders = WrapperProviderFactoriesExtensions.GetDefaultProviderFactories(); - - // Assert - Assert.Collection( - factoryProviders, - factory => Assert.IsType(factory), - factory => - { - var wrapperProviderFactory = Assert.IsType(factory); - Assert.Equal(typeof(ProblemDetails), wrapperProviderFactory.DeclaredType); - Assert.Equal(typeof(ProblemDetailsWrapper), wrapperProviderFactory.WrappingType); - }, - factory => - { - var wrapperProviderFactory = Assert.IsType(factory); - Assert.Equal(typeof(ValidationProblemDetails), wrapperProviderFactory.DeclaredType); - Assert.Equal(typeof(ValidationProblemDetailsWrapper), wrapperProviderFactory.WrappingType); - }); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs deleted file mode 100644 index c067ed4ab9..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/WrapperProviderFactoryTest.cs +++ /dev/null @@ -1,63 +0,0 @@ -// 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 Xunit; - -namespace Microsoft.AspNetCore.Mvc.Formatters.Xml -{ - public class WrapperProviderFactoryTest - { - [Fact] - public void GetProvider_ReturnsNull_IfTypeDoesNotMatch() - { - // Arrange - var provider = new WrapperProviderFactory( - typeof(ProblemDetails), - typeof(ProblemDetailsWrapper), - _ => null); - var context = new WrapperProviderContext(typeof(SerializableError), isSerialization: true); - - // Act - var result = provider.GetProvider(context); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetProvider_ReturnsNull_IfTypeIsSubtype() - { - // Arrange - var provider = new WrapperProviderFactory( - typeof(ProblemDetails), - typeof(ProblemDetailsWrapper), - _ => null); - var context = new WrapperProviderContext(typeof(ValidationProblemDetails), isSerialization: true); - - // Act - var result = provider.GetProvider(context); - - // Assert - Assert.Null(result); - } - - [Fact] - public void GetProvider_ReturnsValue_IfTypeMatches() - { - // Arrange - var expected = new object(); - var providerFactory = new WrapperProviderFactory( - typeof(ProblemDetails), - typeof(ProblemDetailsWrapper), - _ => expected); - var context = new WrapperProviderContext(typeof(ProblemDetails), isSerialization: true); - - // Act - var provider = providerFactory.GetProvider(context); - var result = provider.Wrap(new ProblemDetails()); - - // Assert - Assert.Same(expected, result); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs new file mode 100644 index 0000000000..08c2b0aaf2 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs @@ -0,0 +1,71 @@ +// 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.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class XmlDataContractSerializerMvcOptionsSetupTest + { + [Fact] + public void AddsFormatterMapping() + { + // Arrange + var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); + Assert.Equal("application/xml", mappedContentType); + } + + [Fact] + public void DoesNotOverrideExistingMapping() + { + // Arrange + var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml"); + + // Act + optionsSetup.Configure(options); + + // Assert + var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); + Assert.Equal("text/xml", mappedContentType); + } + + [Fact] + public void AddsInputFormatter() + { + // Arrange + var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.IsType(Assert.Single(options.InputFormatters)); + } + + [Fact] + public void AddsOutputFormatter() + { + // Arrange + var optionsSetup = new XmlDataContractSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.IsType(Assert.Single(options.OutputFormatters)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs new file mode 100644 index 0000000000..d3b8790e64 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs @@ -0,0 +1,71 @@ +// 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.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.Formatters.Xml +{ + public class XmlSerializerMvcOptionsSetupTest + { + [Fact] + public void AddsFormatterMapping() + { + // Arrange + var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); + Assert.Equal("application/xml", mappedContentType); + } + + [Fact] + public void DoesNotOverrideExistingMapping() + { + // Arrange + var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + options.FormatterMappings.SetMediaTypeMappingForFormat("xml", "text/xml"); + + // Act + optionsSetup.Configure(options); + + // Assert + var mappedContentType = options.FormatterMappings.GetMediaTypeMappingForFormat("xml"); + Assert.Equal("text/xml", mappedContentType); + } + + [Fact] + public void AddsInputFormatter() + { + // Arrange + var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.IsType(Assert.Single(options.InputFormatters)); + } + + [Fact] + public void AddsOutputFormatter() + { + // Arrange + var optionsSetup = new XmlSerializerMvcOptionsSetup(Options.Create(new MvcXmlOptions()), NullLoggerFactory.Instance); + var options = new MvcOptions(); + + // Act + optionsSetup.Configure(options); + + // Assert + Assert.IsType(Assert.Single(options.OutputFormatters)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs index d85de4c3c7..5c781c6652 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs @@ -2,12 +2,16 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.AspNetCore.Testing.xunit; +using XmlFormattersWebSite; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -16,10 +20,12 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public XmlDataContractSerializerFormattersWrappingTest(MvcTestFixture fixture) { - Client = fixture.CreateDefaultClient(); + Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(builder => builder.UseStartup()); + Client = Factory.CreateDefaultClient(); } public HttpClient Client { get; } + public WebApplicationFactory Factory { get; } [ConditionalTheory] // Mono issue - https://github.com/aspnet/External/issues/18 @@ -254,6 +260,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests XmlAssert.Equal(expected, content); } + [Fact] + public async Task ProblemDetails_With21Behavior() + { + // Arrange + var expected = "" + + "instance" + + "404" + + "title" + + "correlation" + + "Account1 Account2" + + ""; + + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); + + // Act + var response = await client.GetAsync("/api/XmlDataContractApi/ActionReturningProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + [Fact] public async Task ValidationProblemDetails_IsSerialized() { @@ -302,5 +333,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var content = await response.Content.ReadAsStringAsync(); XmlAssert.Equal(expected, content); } + + [Fact] + public async Task ValidationProblemDetails_With21Behavior() + { + // Arrange + var expected = "" + + "some detail" + + "400" + + "One or more validation errors occurred." + + "some type" + + "correlation" + + "" + + "ErrorValue" + + "" + + ""; + + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); + + // Act + var response = await client.GetAsync("/api/XmlDataContractApi/ActionReturningValidationDetailsWithMetadata"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs index 33c76b98e8..542941cfb8 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs @@ -2,11 +2,15 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Diagnostics; +using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Formatters.Xml; +using Microsoft.AspNetCore.Mvc.Testing; +using XmlFormattersWebSite; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -15,9 +19,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests { public XmlSerializerFormattersWrappingTest(MvcTestFixture fixture) { - Client = fixture.CreateDefaultClient(); + Factory = fixture.Factories.FirstOrDefault() ?? fixture.WithWebHostBuilder(builder => builder.UseStartup()); + Client = Factory.CreateDefaultClient(); } + public WebApplicationFactory Factory { get; } public HttpClient Client { get; } [Theory] @@ -208,6 +214,31 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests } } + [Fact] + public async Task ProblemDetails_With21Behavior() + { + // Arrange + var expected = "" + + "instance" + + "404" + + "title" + + "correlation" + + "Account1 Account2" + + ""; + + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); + + // Act + var response = await client.GetAsync("/api/XmlSerializerApi/ActionReturningProblemDetails"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.NotFound); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } + [Fact] public async Task ProblemDetails_WithExtensionMembers_IsSerialized() { @@ -277,5 +308,33 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var content = await response.Content.ReadAsStringAsync(); XmlAssert.Equal(expected, content); } + + [Fact] + public async Task ValidationProblemDetails_With21Behavior() + { + // Arrange + var expected = "" + + "some detail" + + "400" + + "One or more validation errors occurred." + + "some type" + + "correlation" + + "" + + "ErrorValue" + + "" + + ""; + + var client = Factory + .WithWebHostBuilder(builder => builder.UseStartup()) + .CreateDefaultClient(); + + // Act + var response = await client.GetAsync("/api/XmlSerializerApi/ActionReturningValidationDetailsWithMetadata"); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var content = await response.Content.ReadAsStringAsync(); + XmlAssert.Equal(expected, content); + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs index ef11bd7f1c..615e8685cb 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs @@ -5,12 +5,8 @@ using System; using System.Buffers; using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; -using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; using Microsoft.AspNetCore.Mvc.Internal; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using Microsoft.Extensions.ObjectPool; diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index 409605d730..ea544c1d26 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.AspNetCore.Mvc.Formatters.Xml; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.Extensions.DependencyInjection; @@ -26,7 +27,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest // Arrange var serviceCollection = new ServiceCollection(); AddHostingServices(serviceCollection); - serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_0); + serviceCollection + .AddMvc() + .AddXmlDataContractSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_0); var services = serviceCollection.BuildServiceProvider(); @@ -36,6 +40,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; var razorViewEngineOptions = services.GetRequiredService>().Value; + var xmlOptions = services.GetRequiredService>().Value; // Assert Assert.False(mvcOptions.AllowCombiningAuthorizeFilters); @@ -50,6 +55,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(apiBehaviorOptions.SuppressMapClientErrors); Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); + Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); } [Fact] @@ -58,7 +64,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest // Arrange var serviceCollection = new ServiceCollection(); AddHostingServices(serviceCollection); - serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1); + serviceCollection + .AddMvc() + .AddXmlDataContractSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); var services = serviceCollection.BuildServiceProvider(); @@ -68,6 +77,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; var razorViewEngineOptions = services.GetRequiredService>().Value; + var xmlOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -82,6 +92,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(apiBehaviorOptions.SuppressMapClientErrors); Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); + Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); } [Fact] @@ -90,7 +101,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest // Arrange var serviceCollection = new ServiceCollection(); AddHostingServices(serviceCollection); - serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2); + serviceCollection + .AddMvc() + .AddXmlDataContractSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion.Version_2_2); var services = serviceCollection.BuildServiceProvider(); @@ -100,6 +114,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; var razorViewEngineOptions = services.GetRequiredService>().Value; + var xmlOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -114,6 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(apiBehaviorOptions.SuppressMapClientErrors); Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); + Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); } [Fact] @@ -122,7 +138,10 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest // Arrange var serviceCollection = new ServiceCollection(); AddHostingServices(serviceCollection); - serviceCollection.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Latest); + serviceCollection + .AddMvc() + .AddXmlDataContractSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion.Latest); var services = serviceCollection.BuildServiceProvider(); @@ -132,6 +151,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest var razorPagesOptions = services.GetRequiredService>().Value; var apiBehaviorOptions = services.GetRequiredService>().Value; var razorViewEngineOptions = services.GetRequiredService>().Value; + var xmlOptions = services.GetRequiredService>().Value; // Assert Assert.True(mvcOptions.AllowCombiningAuthorizeFilters); @@ -146,6 +166,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(apiBehaviorOptions.SuppressMapClientErrors); Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); + Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); } // This just does the minimum needed to be able to resolve these options. diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs index dd8f228caa..fc4918943b 100644 --- a/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs +++ b/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs @@ -1,9 +1,12 @@ // 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.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace XmlFormattersWebSite { @@ -22,7 +25,10 @@ namespace XmlFormattersWebSite // Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter // selection. The globally configured formatters rely on custom content-type to perform conneg which does not play // well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller. - objectResult.Formatters.Add(new XmlDataContractSerializerOutputFormatter()); + var mvcOptions = context.HttpContext.RequestServices.GetRequiredService>(); + var xmlFormatter = mvcOptions.Value.OutputFormatters.OfType().First(); + + objectResult.Formatters.Add(xmlFormatter); } } } diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs b/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs index 6ee3ec4708..b6e39ad96b 100644 --- a/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs +++ b/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs @@ -1,9 +1,12 @@ // 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.Linq; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; namespace XmlFormattersWebSite { @@ -22,7 +25,10 @@ namespace XmlFormattersWebSite // Both kinds of Xml serializers are configured for this application and use custom content-types to do formatter // selection. The globally configured formatters rely on custom content-type to perform conneg which does not play // well the ProblemDetails returning filters that defaults to using application/xml. We'll explicitly select the formatter for this controller. - objectResult.Formatters.Add(new XmlSerializerOutputFormatter()); + var mvcOptions = context.HttpContext.RequestServices.GetRequiredService>(); + var xmlFormatter = mvcOptions.Value.OutputFormatters.OfType().First(); + + objectResult.Formatters.Add(xmlFormatter); } } } diff --git a/test/WebSites/XmlFormattersWebSite/Startup.cs b/test/WebSites/XmlFormattersWebSite/Startup.cs index 07bda1e37a..03cd9d9bd2 100644 --- a/test/WebSites/XmlFormattersWebSite/Startup.cs +++ b/test/WebSites/XmlFormattersWebSite/Startup.cs @@ -13,47 +13,85 @@ namespace XmlFormattersWebSite { public class Startup { + public virtual CompatibilityVersion CompatibilityVersion => CompatibilityVersion.Latest; + // Set up application services public void ConfigureServices(IServiceCollection services) { // Add MVC services to the services container services.AddMvc() - .SetCompatibilityVersion(CompatibilityVersion.Latest); + .AddXmlDataContractSerializerFormatters() + .AddXmlSerializerFormatters() + .SetCompatibilityVersion(CompatibilityVersion); services.Configure(options => { - options.InputFormatters.Clear(); - options.OutputFormatters.Clear(); - // Since both XmlSerializer and DataContractSerializer based formatters // have supported media types of 'application/xml' and 'text/xml', it // would be difficult for a test to choose a particular formatter based on - // request information (Ex: Accept header). - // So here we instead clear out the default supported media types and create new - // ones which are distinguishable between formatters. - var xmlSerializerInputFormatter = new XmlSerializerInputFormatter(new MvcOptions()); + // request information (Ex: Accept header). + // We'll configure the ones on MvcOptions to use a distinct set of content types. + + XmlSerializerInputFormatter xmlSerializerInputFormatter = null; + XmlSerializerOutputFormatter xmlSerializerOutputFormatter = null; + XmlDataContractSerializerInputFormatter dcsInputFormatter = null; + XmlDataContractSerializerOutputFormatter dcsOutputFormatter = null; + + for (var i = options.InputFormatters.Count - 1; i >= 0; i--) + { + switch (options.InputFormatters[i]) + { + case XmlSerializerInputFormatter formatter: + xmlSerializerInputFormatter = formatter; + break; + + case XmlDataContractSerializerInputFormatter formatter: + dcsInputFormatter = formatter; + break; + + default: + options.InputFormatters.RemoveAt(i); + break; + } + } + + for (var i = options.OutputFormatters.Count - 1; i >= 0; i--) + { + switch (options.OutputFormatters[i]) + { + case XmlSerializerOutputFormatter formatter: + xmlSerializerOutputFormatter = formatter; + break; + + case XmlDataContractSerializerOutputFormatter formatter: + dcsOutputFormatter = formatter; + break; + + default: + options.OutputFormatters.RemoveAt(i); + break; + } + } + xmlSerializerInputFormatter.SupportedMediaTypes.Clear(); - xmlSerializerInputFormatter.SupportedMediaTypes.Add( - new MediaTypeHeaderValue("application/xml-xmlser")); - xmlSerializerInputFormatter.SupportedMediaTypes.Add( - new MediaTypeHeaderValue("text/xml-xmlser")); + xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-xmlser")); + xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-xmlser")); + xmlSerializerInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml")); - var xmlSerializerOutputFormatter = new XmlSerializerOutputFormatter(); xmlSerializerOutputFormatter.SupportedMediaTypes.Clear(); - xmlSerializerOutputFormatter.SupportedMediaTypes.Add( - new MediaTypeHeaderValue("application/xml-xmlser")); - xmlSerializerOutputFormatter.SupportedMediaTypes.Add( - new MediaTypeHeaderValue("text/xml-xmlser")); + xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-xmlser")); + xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-xmlser")); + xmlSerializerOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml")); - var dcsInputFormatter = new XmlDataContractSerializerInputFormatter(new MvcOptions()); dcsInputFormatter.SupportedMediaTypes.Clear(); dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-dcs")); dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-dcs")); + dcsInputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml")); - var dcsOutputFormatter = new XmlDataContractSerializerOutputFormatter(); dcsOutputFormatter.SupportedMediaTypes.Clear(); dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/xml-dcs")); dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("text/xml-dcs")); + dcsOutputFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/problem+xml")); options.InputFormatters.Add(dcsInputFormatter); options.InputFormatters.Add(xmlSerializerInputFormatter); diff --git a/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs b/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs new file mode 100644 index 0000000000..5b96407d99 --- /dev/null +++ b/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.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. + +using Microsoft.AspNetCore.Mvc; + +namespace XmlFormattersWebSite +{ + public class StartupWith21Compat : Startup + { + public override CompatibilityVersion CompatibilityVersion => CompatibilityVersion.Version_2_1; + } +} + From fb57810f2960730e59db0235207b873c924bc3fd Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 4 Oct 2018 16:58:42 -0700 Subject: [PATCH 296/316] Shortcircuit validation when using default validator providers and no validation metadata is discovered Fixes https://github.com/aspnet/Mvc/issues/5887 --- .../BasicApi/Controllers/PetController.cs | 7 + benchmarkapps/BasicApi/benchmarks.json | 6 + .../ValidationVisitorBenchmarkBase.cs | 75 +++ .../ValidationVisitorByteArrayBenchmark.cs | 42 ++ ...tionVisitorModelWithValidatedProperties.cs | 92 +++ .../ModelBinding/ModelMetadata.cs | 9 + .../MvcCoreServiceCollectionExtensions.cs | 2 + .../MvcCoreMvcOptionsSetup.cs | 16 +- .../Metadata/DefaultModelMetadata.cs | 80 +++ ...HasValidatorsValidationMetadataProvider.cs | 53 ++ .../Metadata/ValidationMetadata.cs | 5 + .../DefaultModelValidatorProvider.cs | 23 +- .../IMetadataBasedModelValidatorProvider.cs | 30 + .../Validation/ValidationVisitor.cs | 18 + .../DataAnnotationsModelValidatorProvider.cs | 27 +- .../Properties/AssemblyInfo.cs | 6 + .../MvcCoreServiceCollectionExtensionsTest.cs | 1 + .../Internal/DefaultObjectValidatorTests.cs | 111 +++- .../Metadata/DefaultModelMetadataTest.cs | 382 +++++++++++ ...alidatorsValidationMetadataProviderTest.cs | 131 ++++ .../DefaultModelValidatorProviderTest.cs | 32 +- .../TestModelMetadataProvider.cs | 7 + .../TestModelValidatorProvider.cs | 2 - ...taAnnotationsModelValidatorProviderTest.cs | 54 +- .../InputObjectValidationTests.cs | 90 +++ .../ActionParametersIntegrationTest.cs | 9 +- ...lidationMetadataProviderIntegrationTest.cs | 53 ++ .../TryUpdateModelIntegrationTest.cs | 12 +- .../TryValidateModelIntegrationTest.cs | 2 - .../ValidationIntegrationTests.cs | 597 ++++++++++++++++++ .../MvcOptionsSetupTest.cs | 4 +- .../MvcServiceCollectionExtensionsTest.cs | 6 +- .../Controllers/TestApiController.cs | 16 + .../Models/BookModelWithNoValidation.cs | 20 + .../Models/RecursiveIdentifier.cs | 11 +- 35 files changed, 1992 insertions(+), 39 deletions(-) create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs create mode 100644 benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs rename src/Microsoft.AspNetCore.Mvc.Core/{Internal => Infrastructure}/MvcCoreMvcOptionsSetup.cs (86%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs rename src/Microsoft.AspNetCore.Mvc.Core/{Internal => ModelBinding/Validation}/DefaultModelValidatorProvider.cs (64%) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs rename src/Microsoft.AspNetCore.Mvc.DataAnnotations/{Internal => }/DataAnnotationsModelValidatorProvider.cs (84%) create mode 100644 test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs rename test/Microsoft.AspNetCore.Mvc.Core.Test/{Internal => ModelBinding/Validation}/DefaultModelValidatorProviderTest.cs (88%) rename test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/{Internal => }/DataAnnotationsModelValidatorProviderTest.cs (79%) create mode 100644 test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs create mode 100644 test/WebSites/FormatterWebSite/Controllers/TestApiController.cs create mode 100644 test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs diff --git a/benchmarkapps/BasicApi/Controllers/PetController.cs b/benchmarkapps/BasicApi/Controllers/PetController.cs index 8a30fb18c2..ed1ff8f5b9 100644 --- a/benchmarkapps/BasicApi/Controllers/PetController.cs +++ b/benchmarkapps/BasicApi/Controllers/PetController.cs @@ -132,6 +132,13 @@ namespace BasicApi.Controllers return new CreatedAtRouteResult("FindPetById", new { id = pet.Id }, pet); } + [Authorize("pet-store-writer")] + [HttpPost("add-pet")] + public ActionResult AddPetWithoutDb(Pet pet) + { + return pet; + } + [Authorize("pet-store-writer")] [HttpPut] public IActionResult EditPet(Pet pet) diff --git a/benchmarkapps/BasicApi/benchmarks.json b/benchmarkapps/BasicApi/benchmarks.json index 7b64057987..aff5eb280d 100644 --- a/benchmarkapps/BasicApi/benchmarks.json +++ b/benchmarkapps/BasicApi/benchmarks.json @@ -44,5 +44,11 @@ "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua" }, "Path": "/pet" + }, + "BasicApi.PostWithoutDb": { + "Path": "/pet/add-pet", + "ClientProperties": { + "Scripts": "https://raw.githubusercontent.com/aspnet/Mvc/release/2.2/benchmarkapps/BasicApi/postJsonWithToken.lua" + } } } diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs new file mode 100644 index 0000000000..cfa12d5f1b --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs @@ -0,0 +1,75 @@ +// 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 BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc.Abstractions; +using Microsoft.AspNetCore.Mvc.DataAnnotations; +using Microsoft.AspNetCore.Mvc.Internal; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.Options; + +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public abstract class ValidationVisitorBenchmarkBase + { + protected const int Iterations = 4; + + protected static readonly IModelValidatorProvider[] ValidatorProviders = new IModelValidatorProvider[] + { + new DefaultModelValidatorProvider(), + new DataAnnotationsModelValidatorProvider( + new ValidationAttributeAdapterProvider(), + Options.Create(new MvcDataAnnotationsLocalizationOptions()), + null), + }; + + protected static readonly CompositeModelValidatorProvider CompositeModelValidatorProvider = new CompositeModelValidatorProvider(ValidatorProviders); + + public abstract object Model { get; } + + public ModelMetadataProvider BaselineModelMetadataProvider { get; private set; } + public ModelMetadataProvider ModelMetadataProvider { get; private set; } + public ModelMetadata BaselineModelMetadata { get; private set; } + public ModelMetadata ModelMetadata { get; private set; } + public ActionContext ActionContext { get; private set; } + public ValidatorCache ValidatorCache { get; private set; } + + [GlobalSetup] + public void Setup() + { + BaselineModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: false); + ModelMetadataProvider = CreateModelMetadataProvider(addHasValidatorsProvider: true); + + BaselineModelMetadata = BaselineModelMetadataProvider.GetMetadataForType(Model.GetType()); + ModelMetadata = ModelMetadataProvider.GetMetadataForType(Model.GetType()); + ActionContext = GetActionContext(); + ValidatorCache = new ValidatorCache(); + } + + protected static ModelMetadataProvider CreateModelMetadataProvider(bool addHasValidatorsProvider) + { + var detailsProviders = new List + { + new DefaultValidationMetadataProvider(), + }; + + if (addHasValidatorsProvider) + { + detailsProviders.Add(new HasValidatorsValidationMetadataProvider(ValidatorProviders)); + } + + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); + return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions())); + } + + protected static ActionContext GetActionContext() + { + return new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + } + } +} diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs new file mode 100644 index 0000000000..8880aa5a05 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs @@ -0,0 +1,42 @@ +// 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 BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public class ValidationVisitorByteArrayBenchmark : ValidationVisitorBenchmarkBase + { + public override object Model { get; } = new byte[30]; + + [Benchmark(Baseline = true, Description = "validation for byte arrays baseline", OperationsPerInvoke = Iterations)] + public void Baseline() + { + // Baseline for validating a byte array of size 30, without the ModelMetadata.HasValidators optimization. + // This is the behavior as of 2.1. + var validationVisitor = new ValidationVisitor( + ActionContext, + CompositeModelValidatorProvider, + ValidatorCache, + BaselineModelMetadataProvider, + new ValidationStateDictionary()); + + validationVisitor.Validate(BaselineModelMetadata, "key", Model); + } + + [Benchmark(Description = "validation for byte arrays", OperationsPerInvoke = Iterations)] + public void HasValidators() + { + // Validating a byte array of size 30, with the ModelMetadata.HasValidators optimization. + var validationVisitor = new ValidationVisitor( + ActionContext, + CompositeModelValidatorProvider, + ValidatorCache, + ModelMetadataProvider, + new ValidationStateDictionary()); + + validationVisitor.Validate(ModelMetadata, "key", Model); + } + } +} diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs new file mode 100644 index 0000000000..58c4127232 --- /dev/null +++ b/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs @@ -0,0 +1,92 @@ +// 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.ComponentModel.DataAnnotations; +using BenchmarkDotNet.Attributes; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; + +namespace Microsoft.AspNetCore.Mvc.Performance +{ + public class ValidationVisitorModelWithValidatedProperties : ValidationVisitorBenchmarkBase + { + public class Person + { + [Required] + public int Id { get; set; } + + [Required] + [StringLength(20)] + public string Name { get; set; } + + public string Description { get; set; } + + public IList
Address { get; set; } + } + + public class Address + { + [Required] + public string Street { get; set; } + + public string Street2 { get; set; } + + public string Type { get; set; } + + [Required] + public string Zip { get; set; } + } + + public override object Model { get; } = new Person + { + Id = 10, + Name = "Test", + Address = new List
+ { + new Address + { + Street = "1 Microsoft Way", + Type = "Work", + Zip = "98056", + }, + new Address + { + Street = "15701 NE 39th St", + Type = "Home", + Zip = "98052", + } + }, + }; + + [Benchmark(Baseline = true, Description = "validation for a model with some validated properties - baseline", OperationsPerInvoke = Iterations)] + public void Visit_TypeWithSomeValidatedProperties_Baseline() + { + // Baseline for validating a typical model with some properties that require validation. + // This executes without the ModelMetadata.HasValidators optimization. + + var validationVisitor = new ValidationVisitor( + ActionContext, + CompositeModelValidatorProvider, + ValidatorCache, + BaselineModelMetadataProvider, + new ValidationStateDictionary()); + + validationVisitor.Validate(BaselineModelMetadata, "key", Model); + } + + [Benchmark(Description = "validation for a model with some validated properties", OperationsPerInvoke = Iterations)] + public void Visit_TypeWithSomeValidatedProperties() + { + // Validating a typical model with some properties that require validation. + // This executes with the ModelMetadata.HasValidators optimization. + var validationVisitor = new ValidationVisitor( + ActionContext, + CompositeModelValidatorProvider, + ValidatorCache, + ModelMetadataProvider, + new ValidationStateDictionary()); + + validationVisitor.Validate(ModelMetadata, "key", Model); + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs index f78815a7d1..8701d4f4a3 100644 --- a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs @@ -325,6 +325,15 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding ///
public abstract bool ValidateChildren { get; } + /// + /// Gets a value that indicates if the model, or one of it's properties, or elements has associatated validators. + /// + /// + /// When , validation can be assume that the model is valid () without + /// inspecting the object graph. + /// + public virtual bool? HasValidators { get; } + /// /// Gets a collection of metadata items for validators. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs index a0c082fdf6..95a4afb21b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs @@ -146,6 +146,8 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Transient, MvcCoreMvcOptionsSetup>()); services.TryAddEnumerable( ServiceDescriptor.Transient, MvcOptionsConfigureCompatibilityOptions>()); + services.TryAddEnumerable( + ServiceDescriptor.Transient, MvcCoreMvcOptionsSetup>()); services.TryAddEnumerable( ServiceDescriptor.Transient, ApiBehaviorOptionsSetup>()); services.TryAddEnumerable( diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs similarity index 86% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs rename to src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs index 4cb4f9f3bd..7b89b69ee6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreMvcOptionsSetup.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs @@ -8,24 +8,25 @@ using System.Threading; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Infrastructure; +using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc { /// /// Sets up default options for . /// - public class MvcCoreMvcOptionsSetup : IConfigureOptions + internal class MvcCoreMvcOptionsSetup : IConfigureOptions, IPostConfigureOptions { private readonly IHttpRequestStreamReaderFactory _readerFactory; private readonly ILoggerFactory _loggerFactory; - // Used in tests public MvcCoreMvcOptionsSetup(IHttpRequestStreamReaderFactory readerFactory) : this(readerFactory, NullLoggerFactory.Instance) { @@ -83,6 +84,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal options.ModelValidatorProviders.Add(new DefaultModelValidatorProvider()); } + public void PostConfigure(string name, MvcOptions options) + { + // HasValidatorsValidationMetadataProvider uses the results of other ValidationMetadataProvider to determine if a model requires + // validation. It is imperative that this executes later than all other metadata provider. We'll register it as part of PostConfigure. + // This should ensure it appears later than all of the details provider registered by MVC and user configured details provider registered + // as part of ConfigureOptions. + options.ModelMetadataDetailsProviders.Add(new HasValidatorsValidationMetadataProvider(options.ModelValidatorProviders)); + } + internal static void ConfigureAdditionalModelMetadataDetailsProviders(IList modelMetadataDetailsProviders) { // Don't bind the Type class by default as it's expensive. A user can override this behavior diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs index bfb1bde8b5..277da94ddd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Runtime.CompilerServices; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata @@ -29,6 +30,7 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata private bool? _isRequired; private ModelPropertyCollection _properties; private bool? _validateChildren; + private bool? _hasValidators; private ReadOnlyCollection _validatorMetadata; /// @@ -427,6 +429,84 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata } } + /// + public override bool? HasValidators + { + get + { + if (!_hasValidators.HasValue) + { + var visited = new HashSet(); + + _hasValidators = CalculateHasValidators(visited, this); + } + + return _hasValidators.Value; + } + } + + internal static bool CalculateHasValidators(HashSet visited, ModelMetadata metadata) + { + RuntimeHelpers.EnsureSufficientExecutionStack(); + + if (metadata?.GetType() != typeof(DefaultModelMetadata)) + { + // The calculation is valid only for DefaultModelMetadata instances. Null, other ModelMetadata instances + // or subtypes of DefaultModelMetadata will be treated as always requiring validation. + return true; + } + + var defaultModelMetadata = (DefaultModelMetadata)metadata; + + if (defaultModelMetadata._hasValidators.HasValue) + { + // Return a previously calculated value if available. + return defaultModelMetadata._hasValidators.Value; + } + + if (defaultModelMetadata.ValidationMetadata.HasValidators != false) + { + // Either the ModelMetadata instance has some validators (HasValidators = true) or it is non-deterministic (HasValidators = null). + // In either case, assume it has validators. + return true; + } + + // Before inspecting properties or elements of a collection, ensure we do not have a cycle. + // Consider a model like so + // + // Employee { BusinessDivision Division; int Id; string Name; } + // BusinessDivision { int Id; List Employees } + // + // If we get to the Employee element from Employee.Division.Employees, we can return false for that instance + // and allow other properties of BusinessDivision and Employee to determine if it has validators. + if (!visited.Add(defaultModelMetadata)) + { + return false; + } + + // We have inspected the current element. Inspect properties or elements that may contribute to this value. + if (defaultModelMetadata.IsEnumerableType) + { + if (CalculateHasValidators(visited, defaultModelMetadata.ElementMetadata)) + { + return true; + } + } + else if (defaultModelMetadata.IsComplexType) + { + foreach (var property in defaultModelMetadata.Properties) + { + if (CalculateHasValidators(visited, property)) + { + return true; + } + } + } + + // We've come this far. The ModelMetadata does not have any validation + return false; + } + /// public override IReadOnlyList ValidatorMetadata { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs new file mode 100644 index 0000000000..6378ba518f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs @@ -0,0 +1,53 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + internal class HasValidatorsValidationMetadataProvider : IValidationMetadataProvider + { + private readonly bool _hasOnlyMetadataBasedValidators; + private readonly IMetadataBasedModelValidatorProvider[] _validatorProviders; + + public HasValidatorsValidationMetadataProvider(IList modelValidatorProviders) + { + if (modelValidatorProviders.Count > 0 && modelValidatorProviders.All(p => p is IMetadataBasedModelValidatorProvider)) + { + _hasOnlyMetadataBasedValidators = true; + _validatorProviders = modelValidatorProviders.Cast().ToArray(); + } + } + + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + if (!_hasOnlyMetadataBasedValidators) + { + return; + } + + for (var i = 0; i < _validatorProviders.Length; i++) + { + var provider = _validatorProviders[i]; + if (provider.HasValidators(context.Key.ModelType, context.ValidationMetadata.ValidatorMetadata)) + { + context.ValidationMetadata.HasValidators = true; + return; + } + } + + if (context.ValidationMetadata.HasValidators == null) + { + context.ValidationMetadata.HasValidators = false; + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs index bd4ff327aa..d205fdf172 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs @@ -41,5 +41,10 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata /// in this list, to be consumed later by an . /// public IList ValidatorMetadata { get; } = new List(); + + /// + /// Gets a value that indicates if the model has validators . + /// + public bool? HasValidators { get; set; } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs similarity index 64% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.cs rename to src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs index 11d5739c02..2174be1417 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelValidatorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs @@ -1,9 +1,10 @@ // 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.ModelBinding.Validation; +using System; +using System.Collections.Generic; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { /// /// A default . @@ -12,7 +13,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal /// The provides validators from /// instances in . /// - public class DefaultModelValidatorProvider : IModelValidatorProvider + internal sealed class DefaultModelValidatorProvider : IMetadataBasedModelValidatorProvider { /// public void CreateValidators(ModelValidatorProviderContext context) @@ -28,13 +29,25 @@ namespace Microsoft.AspNetCore.Mvc.Internal continue; } - var validator = validatorItem.ValidatorMetadata as IModelValidator; - if (validator != null) + if (validatorItem.ValidatorMetadata is IModelValidator validator) { validatorItem.Validator = validator; validatorItem.IsReusable = true; } } } + + public bool HasValidators(Type modelType, IList validatorMetadata) + { + for (var i = 0; i < validatorMetadata.Count; i++) + { + if (validatorMetadata[i] is IModelValidator) + { + return true; + } + } + + return false; + } } } \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs new file mode 100644 index 0000000000..4fb9bd0c6f --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs @@ -0,0 +1,30 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation +{ + /// + /// An that provides instances + /// exclusively using values in or the model type. + /// + /// can be used to statically determine if a given + /// instance can incur any validation. The value for + /// can be calculated if all instances in are . + /// + /// + public interface IMetadataBasedModelValidatorProvider : IModelValidatorProvider + { + /// + /// Gets a value that determines if the can + /// produce any validators given the and . + /// + /// The of the model. + /// The list of metadata items for validators. . + /// + bool HasValidators(Type modelType, IList validatorMetadata); + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index 164928c54a..e62f825887 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -265,6 +265,24 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation CurrentPath.Pop(model); return true; } + // If the metadata indicates that no validators exist AND the aggregate state for the key says that the model graph + // is not invalid (i.e. is one of Unvalidated, Valid, or Skipped) we can safely mark the graph as valid. + else if (metadata.HasValidators == false && + ModelState.GetFieldValidationState(key) != ModelValidationState.Invalid) + { + // No validators will be created for this graph of objects. Mark it as valid if it wasn't previously validated. + var entries = ModelState.FindKeysWithPrefix(key); + foreach (var item in entries) + { + if (item.Value.ValidationState == ModelValidationState.Unvalidated) + { + item.Value.ValidationState = ModelValidationState.Valid; + } + } + + CurrentPath.Pop(model); + return true; + } using (StateManager.Recurse(this, key ?? string.Empty, metadata, model, strategy)) { diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs similarity index 84% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.cs rename to src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs index 5ece07f042..b6266958c3 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidatorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs @@ -2,19 +2,21 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; -namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +namespace Microsoft.AspNetCore.Mvc.DataAnnotations { /// /// An implementation of which provides validators /// for attributes which derive from . It also provides /// a validator for types which implement . /// - public class DataAnnotationsModelValidatorProvider : IModelValidatorProvider + internal sealed class DataAnnotationsModelValidatorProvider : IMetadataBasedModelValidatorProvider { private readonly IOptions _options; private readonly IStringLocalizerFactory _stringLocalizerFactory; @@ -66,8 +68,7 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal continue; } - var attribute = validatorItem.ValidatorMetadata as ValidationAttribute; - if (attribute == null) + if (!(validatorItem.ValidatorMetadata is ValidationAttribute attribute)) { continue; } @@ -98,5 +99,23 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal }); } } + + public bool HasValidators(Type modelType, IList validatorMetadata) + { + if (typeof(IValidatableObject).IsAssignableFrom(modelType)) + { + return true; + } + + for (var i = 0; i < validatorMetadata.Count; i++) + { + if (validatorMetadata[i] is ValidationAttribute) + { + return true; + } + } + + return false; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs index c375950300..a0584f4754 100644 --- a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs +++ b/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs @@ -4,3 +4,9 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.DataAnnotations.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.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.ViewFeatures.Test, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] + +[assembly: InternalsVisibleTo("Microsoft.AspNetCore.Mvc.Performance, PublicKey=0024000004800000940000000602000000240000525341310004000001000100f33a29044fa9d740c9b3213a93e57c84b472c84e0b8a0e1ae48e67a9f8f6de9d5f7f3d52ac23e48ac51801f1dc950abe901da34d2a9e3baadb141a17c77ef3c565dd5ee5054b91cf63bb3c6ab83f72ab3aafe93d0fc3c2348b764fafb0b1c0733de51459aeab46580384bf9d74c4e28164b7cde247f891ba07891c9d872ad2bb")] \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs index ba70d85c6e..e4e56bebd2 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs @@ -248,6 +248,7 @@ namespace Microsoft.AspNetCore.Mvc new Type[] { typeof(MvcOptionsConfigureCompatibilityOptions), + typeof(MvcCoreMvcOptionsSetup), } }, { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs index 5450c507c1..8fad61aadd 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs @@ -1170,11 +1170,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal var modelState = actionContext.ModelState; var validationState = new ValidationStateDictionary(); - var validator = CreateValidator(typeof(List)); + var validator = CreateValidator(typeof(List)); - var model = new List() + var model = new List() { - "15", + new ValidatedModel { Value = "15" }, }; modelState.SetModelValue("userIds[0]", "15", "15"); @@ -1192,6 +1192,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Empty(entry.Errors); } + private class ValidatedModel + { + [Required] + public string Value { get; set; } + } + [Fact] public void Validate_SuppressesValidation_ForExcludedType_Stream() { @@ -1317,7 +1323,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal { model, new ValidationStateEntry() } }; var method = GetType().GetMethod(nameof(Validate_Throws_ForTopLevelMetadataData), BindingFlags.NonPublic | BindingFlags.Instance); - var metadata = MetadataProvider.GetMetadataForParameter(method.GetParameters()[0]); // Act & Assert var ex = Assert.Throws(() => validator.Validate(actionContext, validationState, prefix: string.Empty, model)); @@ -1325,6 +1330,102 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotNull(ex.HelpLink); } + [Fact] + public void Validate_TypeWithoutValidators() + { + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new ModelWithoutValidation(); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + + actionContext.ModelState.SetModelValue("Property1", new ValueProviderResult("value1")); + actionContext.ModelState.SetModelValue("Property2", new ValueProviderResult("value2")); + + // Act + validator.Validate(actionContext, validationState, string.Empty, model); + + // Assert + var modelState = actionContext.ModelState; + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + Assert.True(modelState.IsValid); + + var entry = modelState["Property1"]; + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = modelState["Property2"]; + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + [Fact] + public void Validate_TypeWithoutValidators_DoesNotUpdateValidationState() + { + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new ModelWithoutValidation(); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + + var modelState = actionContext.ModelState; + modelState.SetModelValue("Property1", new ValueProviderResult("value1")); + modelState.SetModelValue("Property2", new ValueProviderResult("value2")); + modelState["Property2"].ValidationState = ModelValidationState.Skipped; + + // Act + validator.Validate(actionContext, validationState, string.Empty, model); + + // Assert + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + Assert.True(modelState.IsValid); + + var entry = modelState["Property1"]; + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = modelState["Property2"]; + Assert.Equal(ModelValidationState.Skipped, entry.ValidationState); + } + + [Fact] + public void Validate_TypeWithoutValidators_DoesNotResetInvalidState() + { + var actionContext = new ActionContext(); + var validator = CreateValidator(); + var model = new ModelWithoutValidation(); + var validationState = new ValidationStateDictionary + { + { model, new ValidationStateEntry() } + }; + + var modelState = actionContext.ModelState; + modelState.SetModelValue("Property1", new ValueProviderResult("value1")); + modelState.SetModelValue("Property2", new ValueProviderResult("value2")); + modelState["Property2"].ValidationState = ModelValidationState.Invalid; + + // Act + validator.Validate(actionContext, validationState, string.Empty, model); + + // Assert + Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState); + Assert.False(modelState.IsValid); + + var entry = modelState["Property1"]; + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = modelState["Property2"]; + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + } + + private class ModelWithoutValidation + { + public string Property1 { get; set; } + + public string Property2 { get; set; } + } + private static DefaultObjectValidator CreateValidator(Type excludedType) { var excludeFilters = new List(); @@ -1352,6 +1453,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private class ThrowingProperty { + [Required] public string WatchOut { get @@ -1520,6 +1622,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Depth = depth; } + [Range(-10, 400)] public int Depth { get; } public int MaxAllowedDepth { get; } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs index 6f3eb0830f..3352890707 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs @@ -6,6 +6,7 @@ using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Linq; +using System.Reflection; using System.Xml; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; @@ -909,6 +910,351 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata metadataProvider.VerifyAll(); } + [Fact] + public void CalculateHasValidators_ParameterMetadata_TypeHasNoValidators() + { + // Arrange + var parameter = GetType() + .GetMethod(nameof(CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod), BindingFlags.Static | BindingFlags.NonPublic) + .GetParameters()[0]; + var modelIdentity = ModelMetadataIdentity.ForParameter(parameter); + var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of(), false); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.False(result); + } + + private static void CalculateHasValidators_ParameterMetadata_TypeHasNoValidatorsMethod(string model) { } + + [Fact] + public void CalculateHasValidators_PropertyMetadata_TypeHasNoValidators() + { + // Arrange + var property = GetType() + .GetProperty(nameof(CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty), BindingFlags.Static | BindingFlags.NonPublic); + var modelIdentity = ModelMetadataIdentity.ForProperty(property.PropertyType, property.Name, GetType()); + var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of(), false); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.False(result); + } + + private static int CalculateHasValidators_PropertyMetadata_TypeHasNoValidatorsProperty { get; set; } + + [Fact] + public void CalculateHasValidators_TypeWithoutProperties_TypeHasNoValidators() + { + // Arrange + var modelIdentity = ModelMetadataIdentity.ForType(typeof(string)); + var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of(), false); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.False(result); + } + + [Fact] + public void CalculateHasValidators_SimpleType_TypeHasValidators() + { + // Arrange + var modelIdentity = ModelMetadataIdentity.ForType(typeof(string)); + var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of(), true); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_ReturnsTrue_SimpleType_TypeHasNonDeterministicValidators() + { + // Arrange + var modelIdentity = ModelMetadataIdentity.ForType(typeof(string)); + var modelMetadata = CreateModelMetadata(modelIdentity, Mock.Of(), null); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_TypeWithProperties_PropertyIsNotDefaultModelMetadata() + { + // Arrange + var modelType = typeof(TypeWithProperties); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); + var propertyMetadata = new Mock(propertyIdentity); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { propertyMetadata.Object, }) + .Verifiable(); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_TypeWithProperties_HasValidatorForAnyPropertyIsTrue() + { + // Arrange + var modelType = typeof(TypeWithProperties); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); + var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false); + + var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), typeof(string)); + var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, true); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { property1Metadata, property2Metadata }) + .Verifiable(); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_TypeWithProperties_HasValidatorsForPropertyIsNotDeterminstic() + { + // Arrange + var modelType = typeof(TypeWithProperties); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var propertyIdentity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), typeof(string)); + var propertyMetadata = CreateModelMetadata(propertyIdentity, metadataProvider.Object, null); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { propertyMetadata, }) + .Verifiable(); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_TypeWithProperties_HasValidatorForAllPropertiesIsFalse() + { + // Arrange + var modelType = typeof(TypeWithProperties); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var property1Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetPublicSetProperty), modelType); + var property1Metadata = CreateModelMetadata(property1Identity, metadataProvider.Object, false); + + var property2Identity = ModelMetadataIdentity.ForProperty(typeof(int), nameof(TypeWithProperties.PublicGetProtectedSetProperty), modelType); + var property2Metadata = CreateModelMetadata(property2Identity, metadataProvider.Object, false); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { property1Metadata, property2Metadata }) + .Verifiable(); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.False(result); + } + + [Fact] + public void CalculateHasValidators_SelfReferencingType_HasValidatorOnNestedProperty() + { + // Arrange + var modelType = typeof(Employee); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); + var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); + var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); + var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); + var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); + var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List), nameof(Employee.Employees), modelType); + var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); + + var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); + var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false); + var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); + var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, true); // BusinessUnit.Id has validators. + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, }) + .Verifiable(); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit))) + .Returns(new[] { unitHeadMetadata, unitIdMetadata, }) + .Verifiable(); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_SelfReferencingType_HasValidatorOnSelfReferencedProperty() + { + // Arrange + var modelType = typeof(Employee); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); + var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); + var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); + var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); + var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); + var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List), nameof(Employee.Employees), modelType); + var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); + + var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); + var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, true); // BusinessUnit.Head has validators + var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); + var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesMetadata, }); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit))) + .Returns(new[] { unitHeadMetadata, unitIdMetadata, }); + + metadataProvider + .Setup(mp => mp.GetMetadataForType(modelType)) + .Returns(modelMetadata); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_CollectionElementHasValidators() + { + // Arrange + var modelType = typeof(Employee); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); + var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + var employeeEmployees = ModelMetadataIdentity.ForProperty(typeof(List), nameof(Employee.Employees), modelType); + var employeeEmployeesMetadata = CreateModelMetadata(employeeEmployees, metadataProvider.Object, false); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { employeeIdMetadata, employeeEmployeesMetadata, }); + + metadataProvider + .Setup(mp => mp.GetMetadataForType(modelType)) + .Returns(CreateModelMetadata(modelIdentity, metadataProvider.Object, true)); // Employees.Employee has validators + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.True(result); + } + + [Fact] + public void CalculateHasValidators_SelfReferencingType_NoValidatorsInGraph() + { + // Arrange + var modelType = typeof(Employee); + var modelIdentity = ModelMetadataIdentity.ForType(modelType); + var metadataProvider = new Mock(); + var modelMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + + var employeeId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(Employee.Id), modelType); + var employeeIdMetadata = CreateModelMetadata(modelIdentity, metadataProvider.Object, false); + var employeeUnit = ModelMetadataIdentity.ForProperty(typeof(BusinessUnit), nameof(Employee.Unit), modelType); + var employeeUnitMetadata = CreateModelMetadata(employeeUnit, metadataProvider.Object, false); + var employeeManager = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(Employee.Unit), modelType); + var employeeManagerMetadata = CreateModelMetadata(employeeManager, metadataProvider.Object, false); + var employeeEmployeesId = ModelMetadataIdentity.ForProperty(typeof(List), nameof(Employee.Employees), modelType); + var employeeEmployeesIdMetadata = CreateModelMetadata(employeeEmployeesId, metadataProvider.Object, false); + + var unitHead = ModelMetadataIdentity.ForProperty(typeof(Employee), nameof(BusinessUnit.Head), modelType); + var unitHeadMetadata = CreateModelMetadata(unitHead, metadataProvider.Object, false); + var unitId = ModelMetadataIdentity.ForProperty(typeof(int), nameof(BusinessUnit.Id), modelType); + var unitIdMetadata = CreateModelMetadata(unitId, metadataProvider.Object, false); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(modelType)) + .Returns(new[] { employeeIdMetadata, employeeUnitMetadata, employeeManagerMetadata, employeeEmployeesIdMetadata, }); + + metadataProvider + .Setup(mp => mp.GetMetadataForProperties(typeof(BusinessUnit))) + .Returns(new[] { unitHeadMetadata, unitIdMetadata, }); + + metadataProvider + .Setup(mp => mp.GetMetadataForType(modelType)) + .Returns(modelMetadata); + + // Act + var result = DefaultModelMetadata.CalculateHasValidators(new HashSet(), modelMetadata); + + // Assert + Assert.False(result); + } + + private static DefaultModelMetadata CreateModelMetadata( + ModelMetadataIdentity modelIdentity, + IModelMetadataProvider metadataProvider, + bool? hasValidators) + { + return new DefaultModelMetadata( + metadataProvider, + new SetHasValidatorsCompositeMetadataDetailsProvider { HasValidators = hasValidators }, + new DefaultMetadataDetails(modelIdentity, new ModelAttributes(new object[0], new object[0], new object[0]))); + } + private void ActionMethod(string input) { } @@ -921,5 +1267,41 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata public int PublicGetPublicSetProperty { get; set; } } + + public class Employee + { + public int Id { get; set; } + + public BusinessUnit Unit { get; set; } + + public Employee Manager { get; set; } + + public List Employees { get; set; } + } + + public class BusinessUnit + { + public Employee Head { get; set; } + + public int Id { get; set; } + } + + private class SetHasValidatorsCompositeMetadataDetailsProvider : ICompositeMetadataDetailsProvider + { + public bool? HasValidators { get; set; } + + public void CreateBindingMetadata(BindingMetadataProviderContext context) + { + } + + public void CreateDisplayMetadata(DisplayMetadataProviderContext context) + { + } + + public void CreateValidationMetadata(ValidationMetadataProviderContext context) + { + context.ValidationMetadata.HasValidators = HasValidators; + } + } } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs new file mode 100644 index 0000000000..94ebad7b3b --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs @@ -0,0 +1,131 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Moq; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Metadata +{ + public class HasValidatorsValidationMetadataProviderTest + { + [Fact] + public void CreateValidationMetadata_DoesNotSetHasValidators_IfNonMetadataBasedProviderExists() + { + // Arrange + var validationProviders = new IModelValidatorProvider[] + { + new DefaultModelValidatorProvider(), + Mock.Of(), + }; + var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders); + + var key = ModelMetadataIdentity.ForType(typeof(object)); + var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]); + var context = new ValidationMetadataProviderContext(key, modelAttributes); + + // Act + metadataProvider.CreateValidationMetadata(context); + + // Assert + Assert.Null(context.ValidationMetadata.HasValidators); + } + + [Fact] + public void CreateValidationMetadata_DoesNotSetHasValidators_IfProviderIsConfigured() + { + // Arrange + var validationProviders = new IModelValidatorProvider[0]; + var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders); + + var key = ModelMetadataIdentity.ForType(typeof(object)); + var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]); + var context = new ValidationMetadataProviderContext(key, modelAttributes); + + // Act + metadataProvider.CreateValidationMetadata(context); + + // Assert + Assert.Null(context.ValidationMetadata.HasValidators); + } + + [Fact] + public void CreateValidationMetadata_SetsHasValidatorsToTrue_IfProviderReturnsTrue() + { + // Arrange + var metadataBasedModelValidatorProvider = new Mock(); + metadataBasedModelValidatorProvider.Setup(p => p.HasValidators(typeof(object), It.IsAny>())) + .Returns(true) + .Verifiable(); + + var validationProviders = new IModelValidatorProvider[] + { + new DefaultModelValidatorProvider(), + metadataBasedModelValidatorProvider.Object, + + }; + var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders); + + var key = ModelMetadataIdentity.ForType(typeof(object)); + var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]); + var context = new ValidationMetadataProviderContext(key, modelAttributes); + + // Act + metadataProvider.CreateValidationMetadata(context); + + // Assert + Assert.True(context.ValidationMetadata.HasValidators); + metadataBasedModelValidatorProvider.Verify(); + } + + [Fact] + public void CreateValidationMetadata_SetsHasValidatorsToFalse_IfNoProviderReturnsTrue() + { + // Arrange + var provider = Mock.Of(p => p.HasValidators(typeof(object), It.IsAny>()) == false); + var validationProviders = new IModelValidatorProvider[] + { + new DefaultModelValidatorProvider(), + provider, + }; + var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders); + + var key = ModelMetadataIdentity.ForType(typeof(object)); + var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]); + var context = new ValidationMetadataProviderContext(key, modelAttributes); + + // Act + metadataProvider.CreateValidationMetadata(context); + + // Assert + Assert.False(context.ValidationMetadata.HasValidators); + } + + [Fact] + public void CreateValidationMetadata_DoesNotOverrideExistingHasValidatorsValue() + { + // Arrange + var provider = Mock.Of(p => p.HasValidators(typeof(object), It.IsAny>()) == false); + var validationProviders = new IModelValidatorProvider[] + { + new DefaultModelValidatorProvider(), + provider, + }; + var metadataProvider = new HasValidatorsValidationMetadataProvider(validationProviders); + + var key = ModelMetadataIdentity.ForType(typeof(object)); + var modelAttributes = new ModelAttributes(new object[0], new object[0], new object[0]); + var context = new ValidationMetadataProviderContext(key, modelAttributes); + + // Initialize this value. + context.ValidationMetadata.HasValidators = true; + + // Act + metadataProvider.CreateValidationMetadata(context); + + // Assert + Assert.True(context.ValidationMetadata.HasValidators); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultModelValidatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs similarity index 88% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultModelValidatorProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs index 680d46ff30..bc8e9d4aa4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs @@ -6,11 +6,9 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Xunit; -namespace Microsoft.AspNetCore.Mvc.Internal +namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation { // Integration tests for the default configuration of ModelMetadata and Validation providers public class DefaultModelValidatorProviderTest @@ -145,6 +143,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Single(validatorItems, v => ((DataAnnotationsModelValidator)v.Validator).Attribute is StringLengthAttribute); } + [Fact] + public void HasValidators_ReturnsTrue_IfMetadataIsIModelValidator() + { + // Arrange + var validatorProvider = new DefaultModelValidatorProvider(); + var attributes = new object[] { new RequiredAttribute(), new CustomModelValidatorAttribute(), new BindRequiredAttribute(), }; + + // Act + var result = validatorProvider.HasValidators(typeof(object), attributes); + + // Assert + Assert.True(result); + } + + [Fact] + public void HasValidators_ReturnsFalse_IfNoMetadataIsIModelValidator() + { + // Arrange + var validatorProvider = new DefaultModelValidatorProvider(); + var attributes = new object[] { new RequiredAttribute(), new BindRequiredAttribute(), }; + + // Act + var result = validatorProvider.HasValidators(typeof(object), attributes); + + // Assert + Assert.False(result); + } + private static IList GetValidatorItems(ModelMetadata metadata) { return metadata.ValidatorMetadata.Select(v => new ValidatorItem(v)).ToList(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs index 2548dc26c3..684875d565 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs @@ -8,6 +8,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; using Xunit; @@ -37,6 +38,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding MvcCoreMvcOptionsSetup.ConfigureAdditionalModelMetadataDetailsProviders(detailsProviders); + var validationProviders = TestModelValidatorProvider.CreateDefaultProvider(); + detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders)); + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions())); } @@ -57,6 +61,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding detailsProviders.AddRange(providers); + var validationProviders = TestModelValidatorProvider.CreateDefaultProvider(); + detailsProviders.Add(new HasValidatorsValidationMetadataProvider(validationProviders.ValidatorProviders)); + var compositeDetailsProvider = new DefaultCompositeMetadataDetailsProvider(detailsProviders); return new DefaultModelMetadataProvider(compositeDetailsProvider, Options.Create(new MvcOptions())); } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs index 230f7970b4..4dab51dc37 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs @@ -3,8 +3,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.DataAnnotations; -using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; -using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.Localization; using Microsoft.Extensions.Options; diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs similarity index 79% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorProviderTest.cs rename to test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs index e0a156e8b1..4889e7c4da 100644 --- a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs @@ -1,16 +1,18 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.Linq; +using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.Extensions.Options; using Moq; using Xunit; -namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal +namespace Microsoft.AspNetCore.Mvc.DataAnnotations { public class DataAnnotationsModelValidatorProviderTest { @@ -110,6 +112,56 @@ namespace Microsoft.AspNetCore.Mvc.DataAnnotations.Internal Assert.Single(providerContext.Results); } + [Fact] + public void HasValidators_ReturnsTrue_IfModelIsIValidatableObject() + { + // Arrange + var provider = GetProvider(); + var mockValidatable = Mock.Of(); + + // Act + var result = provider.HasValidators(mockValidatable.GetType(), Array.Empty()); + + // Assert + Assert.True(result); + } + + [Fact] + public void HasValidators_ReturnsTrue_IfMetadataContainsValidationAttribute() + { + // Arrange + var provider = GetProvider(); + var attributes = new object[] { new BindNeverAttribute(), new DummyValidationAttribute() }; + + // Act + var result = provider.HasValidators(typeof(object), attributes); + + // Assert + Assert.True(result); + } + + [Fact] + public void HasValidators_ReturnsFalse_IfNoDataAnnotationsValidationIsAvailable() + { + // Arrange + var provider = GetProvider(); + var attributes = new object[] { new BindNeverAttribute(), }; + + // Act + var result = provider.HasValidators(typeof(object), attributes); + + // Assert + Assert.False(result); + } + + private static DataAnnotationsModelValidatorProvider GetProvider() + { + return new DataAnnotationsModelValidatorProvider( + new ValidationAttributeAdapterProvider(), + Options.Create(new MvcDataAnnotationsLocalizationOptions()), + stringLocalizerFactory: null); + } + private IList GetValidatorItems(ModelMetadata metadata) { var items = new List(metadata.ValidatorMetadata.Count); diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs index 138e70f6c8..a56ee08b8e 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs @@ -262,5 +262,95 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var ex = await Assert.ThrowsAsync(() => Client.SendAsync(requestMessage)); Assert.Equal(expected, ex.Message); } + + [Fact] + public async Task ErrorsDeserializingMalformedJson_AreReportedForModelsWithoutAnyValidationAttributes() + { + // This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation + // errors from json serialization errors + // Arrange + var input = "{Id = \"This string is incomplete"; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation") + { + Content = new StringContent(input, Encoding.UTF8, "application/json"), + }; + + // Act + var response = await Client.SendAsync(requestMessage); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var responseContent = await response.Content.ReadAsStringAsync(); + var validationProblemDetails = JsonConvert.DeserializeObject(responseContent); + + Assert.Collection( + validationProblemDetails.Errors, + error => + { + Assert.Empty(error.Key); + Assert.Equal(new[] { "Invalid character after parsing property name. Expected ':' but got: =. Path '', line 1, position 4." }, error.Value); + }); + } + + [Fact] + public async Task JsonValidationErrors_AreReportedForModelsWithoutAnyValidationAttributes() + { + // This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation + // errors from json serialization errors + // Arrange + var input = "{Id: \"0c92bb85-cfaf-4344-8a9d-f92e88716861\"}"; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation") + { + Content = new StringContent(input, Encoding.UTF8, "application/json"), + }; + + // Act + var response = await Client.SendAsync(requestMessage); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var responseContent = await response.Content.ReadAsStringAsync(); + var validationProblemDetails = JsonConvert.DeserializeObject(responseContent); + + Assert.Collection( + validationProblemDetails.Errors, + error => + { + Assert.Empty(error.Key); + Assert.Equal(new[] { "Required property 'isbn' not found in JSON. Path '', line 1, position 44." }, error.Value); + }); + } + + [Fact] + public async Task ErrorsDeserializingMalformedXml_AreReportedForModelsWithoutAnyValidationAttributes() + { + // This test verifies that for a model with ModelMetadata.HasValidators = false, we continue to get an invalid ModelState + validation + // errors from json serialization errors + // Arrange + var input = "" + + "" + + "Incomplete element" + + ""; + var requestMessage = new HttpRequestMessage(HttpMethod.Post, "TestApi/PostBookWithNoValidation") + { + Content = new StringContent(input, Encoding.UTF8, "application/xml"), + }; + + // Act + var response = await Client.SendAsync(requestMessage); + + // Assert + await response.AssertStatusCodeAsync(HttpStatusCode.BadRequest); + var responseContent = await response.Content.ReadAsStringAsync(); + var validationProblemDetails = JsonConvert.DeserializeObject(responseContent); + + Assert.Collection( + validationProblemDetails.Errors, + error => + { + Assert.Empty(error.Key); + Assert.Equal(new[] { "An error occurred while deserializing input data." }, error.Value); + }); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs index fcd5923101..6f84a74c88 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs @@ -111,13 +111,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests // Read-only collection should not be updated. Assert.Empty(boundModel.Address); - // ModelState (data is can't be validated). - Assert.False(modelState.IsValid); + Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); - Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); + Assert.Equal(ModelValidationState.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); Assert.Equal("SomeStreet", state.AttemptedValue); } @@ -292,12 +291,12 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Empty(boundModel.Address); // ModelState (data cannot be validated). - Assert.False(modelState.IsValid); + Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("prefix.Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); - Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); + Assert.Equal(ModelValidationState.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.AttemptedValue); Assert.Equal("SomeStreet", state.RawValue); } diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs new file mode 100644 index 0000000000..102ddff705 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs @@ -0,0 +1,53 @@ +// 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.Linq; +using Microsoft.AspNetCore.Mvc.ModelBinding; +using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.ObjectPool; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.IntegrationTests +{ + public class HasValidatorsValidationMetadataProviderIntegrationTest + { + [Fact] + public void HasValidatorsValidationMetadataProvider_IsRegisteredAfterOtherMetadataProviders() + { + // HasValidatorsValidationMetadataProvider uses values populated by other details providers to query validator providers + // This test ensures all other detail providers have had an opportunity to modify validation metadata first. + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddSingleton(); + serviceCollection.AddMvc(); + var services = serviceCollection.BuildServiceProvider(); + + // Act + var options = services.GetRequiredService>(); + + Assert.IsType(options.Value.ModelMetadataDetailsProviders.Last()); + } + + [Fact] + public void HasValidatorsValidationMetadataProvider_IsRegisteredAfterUserSpecifiedMetadataProvider() + { + // Arrange + var serviceCollection = new ServiceCollection(); + serviceCollection.AddLogging(); + serviceCollection.AddSingleton(); + serviceCollection.AddMvc(mvcOptions => + { + mvcOptions.ModelMetadataDetailsProviders.Add(new SuppressChildValidationMetadataProvider(typeof(IQueryable))); + }); + var services = serviceCollection.BuildServiceProvider(); + + // Act + var options = services.GetRequiredService>(); + + Assert.IsType(options.Value.ModelMetadataDetailsProviders.Last()); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs index 200d95914d..0bf1131a36 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs @@ -373,15 +373,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var result = await TryUpdateModelAsync(model, string.Empty, testContext); // Assert - Assert.False(result); + Assert.True(result); // ModelState - Assert.False(modelState.IsValid); + Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); - Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); + Assert.Equal(ModelValidationState.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); Assert.Equal("SomeStreet", state.AttemptedValue); } @@ -402,15 +402,15 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var result = await TryUpdateModelAsync(model, "prefix", testContext); // Assert - Assert.False(result); + Assert.True(result); // ModelState - Assert.False(modelState.IsValid); + Assert.True(modelState.IsValid); var entry = Assert.Single(modelState); Assert.Equal("prefix.Address[0].Street", entry.Key); var state = entry.Value; Assert.NotNull(state); - Assert.Equal(ModelValidationState.Unvalidated, state.ValidationState); + Assert.Equal(ModelValidationState.Valid, state.ValidationState); Assert.Equal("SomeStreet", state.RawValue); Assert.Equal("SomeStreet", state.AttemptedValue); } diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs index d9bb172f0e..cae150842f 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs @@ -158,7 +158,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests Assert.Equal(2, modelStateErrors.Count); AssertErrorEquals("Property", modelStateErrors["Message"]); AssertErrorEquals("Model", modelStateErrors[""]); - } [Fact] @@ -183,7 +182,6 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests var modelStateErrors = GetModelStateErrors(modelState); Assert.Single(modelStateErrors); // single error from the required attribute AssertErrorEquals("Property", modelStateErrors.Single().Value); - } [ModelLevelError] diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs index 9482cf918e..25deb2477d 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs @@ -2,9 +2,13 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.ComponentModel.DataAnnotations; using System.IO; +using System.Linq; +using System.Reflection; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -14,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Metadata; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Xunit; @@ -1488,6 +1493,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public string Control { get; set; } [ValidateSometimes(nameof(Control))] + [Range(0, 10)] public int ControlLength => Control.Length; } @@ -1571,6 +1577,53 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests }); } + // This type has a IPropertyValidationFilter declared on a property, but no validators. + // We should expect validation to short-circuit + private class ValidateSomePropertiesSometimesWithoutValidation + { + public string Control { get; set; } + + [ValidateSometimes(nameof(Control))] + public int ControlLength => Control.Length; + } + + [Fact] + public async Task PropertyToSometimesSkip_IsNotValidated_IfNoValidationAttributesExistButPropertyValidationFilterExists() + { + // Arrange + var parameter = new ParameterDescriptor + { + Name = "parameter", + ParameterType = typeof(ValidateSomePropertiesSometimesWithoutValidation), + }; + + var testContext = ModelBindingTestHelper.GetTestContext(); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); + var modelState = testContext.ModelState; + + // Add an entry for the ControlLength property so that we can observe Skipped versus Valid states. + modelState.SetModelValue( + nameof(ValidateSomePropertiesSometimes.ControlLength), + rawValue: null, + attemptedValue: null); + + // Act + var result = await parameterBinder.BindModelAsync(parameter, testContext); + + // Assert + Assert.True(result.IsModelSet); + var model = Assert.IsType(result.Model); + Assert.Null(model.Control); + + // Note this Exception is not thrown earlier. + Assert.Throws(() => model.ControlLength); + + Assert.True(modelState.IsValid); + var kvp = Assert.Single(modelState); + Assert.Equal(nameof(ValidateSomePropertiesSometimesWithoutValidation.ControlLength), kvp.Key); + Assert.Equal(ModelValidationState.Valid, kvp.Value.ValidationState); + } + private class Order11 { public IEnumerable
ShippingAddresses { get; set; } @@ -1838,6 +1891,550 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests public string Message { get; set; } } + [Fact] + public async Task Validation_NoAttributeInGraphOfObjects_WithDefaultValidatorProviders() + { + // Arrange + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Order12), + BindingInfo = new BindingInfo + { + BindingSource = BindingSource.Body + }, + }; + + var input = new Order12 + { + Id = 10, + OrderFile = new byte[40], + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.Body = new MemoryStream(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(input))); + request.ContentType = "application/json"; + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(input.Id, model.Id); + Assert.Equal(input.OrderFile, model.OrderFile); + Assert.Null(model.RelatedOrders); + + Assert.Empty(modelState); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + } + + private class Order12 + { + public int Id { get; set; } + + public byte[] OrderFile { get; set; } + + public IList RelatedOrders { get; set; } + } + + [Fact] + public async Task Validation_ListOfType_NoValidatorOnParameter() + { + // Arrange + var parameterInfo = GetType().GetMethod(nameof(Validation_ListOfType_NoValidatorOnParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Static) + .GetParameters() + .First(); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = parameterInfo.Name, + ParameterType = parameterInfo.ParameterType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?[0]=1&[1]=2"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(new[] { 1, 2 }, model); + + Assert.False(modelMetadata.HasValidators); + + Assert.True(modelState.IsValid); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "[0]").Value; + Assert.Equal("1", entry.AttemptedValue); + Assert.Equal("1", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "[1]").Value; + Assert.Equal("2", entry.AttemptedValue); + Assert.Equal("2", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + private static void Validation_ListOfType_NoValidatorOnParameterTestMethod(List parameter) { } + + [Fact] + public async Task Validation_ListOfType_ValidatorOnParameter() + { + // Arrange + var parameterInfo = GetType().GetMethod(nameof(Validation_ListOfType_ValidatorOnParameterTestMethod), BindingFlags.NonPublic | BindingFlags.Static) + .GetParameters() + .First(); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = parameterInfo.Name, + ParameterType = parameterInfo.ParameterType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?[0]=1&[1]=2"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(new[] { 1, 2 }, model); + + Assert.True(modelMetadata.HasValidators); + + Assert.False(modelState.IsValid); + Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "").Value; + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "[0]").Value; + Assert.Equal("1", entry.AttemptedValue); + Assert.Equal("1", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "[1]").Value; + Assert.Equal("2", entry.AttemptedValue); + Assert.Equal("2", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + private static void Validation_ListOfType_ValidatorOnParameterTestMethod([ConsistentMinLength(3)] List parameter) { } + + private class ConsistentMinLength : ValidationAttribute + { + private readonly int _length; + + public ConsistentMinLength(int length) + { + _length = length; + } + + public override bool IsValid(object value) + { + return value is ICollection collection && collection.Count >= _length; + } + } + + [Fact] + public async Task Validation_CollectionOfType_ValidatorOnElement() + { + // Arrange + var parameterInfo = GetType().GetMethod(nameof(Validation_CollectionOfType_ValidatorOnElementTestMethod), BindingFlags.NonPublic | BindingFlags.Static) + .GetParameters() + .First(); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = parameterInfo.Name, + ParameterType = parameterInfo.ParameterType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?p[0].Id=1&p[1].Id=2"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Equal(1, model[0].Id); + Assert.Equal(2, model[1].Id); + + Assert.True(modelMetadata.HasValidators); + + Assert.False(modelState.IsValid); + Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "p[0].Id").Value; + Assert.Equal("1", entry.AttemptedValue); + Assert.Equal("1", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "p[1]").Value; + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "p[1].Id").Value; + Assert.Equal("2", entry.AttemptedValue); + Assert.Equal("2", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + private static void Validation_CollectionOfType_ValidatorOnElementTestMethod(Collection p) { } + + public class InvalidEvenIds : IValidatableObject + { + public int Id { get; set; } + + public IEnumerable Validate(ValidationContext validationContext) + { + if (Id % 2 == 0) + { + yield return new ValidationResult("Failed validation"); + } + } + } + + [Fact] + public async Task Validation_DictionaryType_NoValidators() + { + // Arrange + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(IDictionary) + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value=10"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Collection( + model.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("key0", kvp.Key); + Assert.Equal(10, kvp.Value); + }); + + Assert.True(modelState.IsValid); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value; + Assert.Equal("key0", entry.AttemptedValue); + Assert.Equal("key0", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value").Value; + Assert.Equal("10", entry.AttemptedValue); + Assert.Equal("10", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + [Fact] + public async Task Validation_DictionaryType_ValueHasValidators() + { + // Arrange + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(Dictionary) + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?parameter[0].Key=key0¶meter[0].Value.NeverValidProperty=value0"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType>(modelBindingResult.Model); + Assert.Collection( + model.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("key0", kvp.Key); + Assert.Equal("value0", kvp.Value.NeverValidProperty); + }); + + Assert.False(modelState.IsValid); + Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "parameter[0].Key").Value; + Assert.Equal("key0", entry.AttemptedValue); + Assert.Equal("key0", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value.NeverValidProperty").Value; + Assert.Equal("value0", entry.AttemptedValue); + Assert.Equal("value0", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "parameter[0].Value").Value; + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + Assert.Single(entry.Errors); + } + + [Fact] + public async Task Validation_TopLevelProperty_NoValidation() + { + // Arrange + var modelType = typeof(Validation_TopLevelPropertyController); + var propertyInfo = modelType.GetProperty(nameof(Validation_TopLevelPropertyController.Model)); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, propertyInfo.PropertyType); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = propertyInfo.Name, + ParameterType = propertyInfo.PropertyType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?Model.Id=12"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(12, model.Id); + + Assert.False(modelMetadata.HasValidators); + + Assert.True(modelState.IsValid); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "Model.Id").Value; + Assert.Equal("12", entry.AttemptedValue); + Assert.Equal("12", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + public class Validation_TopLevelPropertyModel + { + public int Id { get; set; } + } + + private class Validation_TopLevelPropertyController + { + public Validation_TopLevelPropertyModel Model { get; set; } + } + + [Fact] + public async Task Validation_TopLevelProperty_ValidationOnProperty() + { + // Arrange + var modelType = typeof(Validation_TopLevelProperty_ValidationOnPropertyController); + var propertyInfo = modelType.GetProperty(nameof(Validation_TopLevelProperty_ValidationOnPropertyController.Model)); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForProperty(propertyInfo, propertyInfo.PropertyType); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = propertyInfo.Name, + ParameterType = propertyInfo.PropertyType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?Model.Id=12"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(12, model.Id); + + Assert.True(modelMetadata.HasValidators); + + Assert.False(modelState.IsValid); + Assert.Equal(ModelValidationState.Invalid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "Model.Id").Value; + Assert.Equal("12", entry.AttemptedValue); + Assert.Equal("12", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + + entry = Assert.Single(modelState, e => e.Key == "Model").Value; + Assert.Equal(ModelValidationState.Invalid, entry.ValidationState); + } + + public class Validation_TopLevelProperty_ValidationOnPropertyController + { + [CustomValidation(typeof(Validation_TopLevelProperty_ValidationOnPropertyController), nameof(Validate))] + public Validation_TopLevelPropertyModel Model { get; set; } + + public static ValidationResult Validate(ValidationContext context) + { + return new ValidationResult("Invalid result"); + } + } + + [Fact] + public async Task Validation_InfinitelyRecursiveType_NoValidators() + { + // Arrange + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(); + var parameter = new ParameterDescriptor() + { + Name = "parameter", + ParameterType = typeof(RecursiveModel) + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?Property1=8"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(8, model.Property1); + + Assert.True(modelState.IsValid); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "Property1").Value; + Assert.Equal("8", entry.AttemptedValue); + Assert.Equal("8", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + public class RecursiveModel + { + public int Property1 { get; set; } + + public RecursiveModel Property2 { get; set; } + + public RecursiveModel Property3 => new RecursiveModel { Property1 = Property1 }; + } + + [Fact] + public async Task Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameter() + { + // Arrange + var parameterInfo = GetType().GetMethod(nameof(Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod), BindingFlags.NonPublic | BindingFlags.Static) + .GetParameters() + .First(); + + var modelMetadataProvider = TestModelMetadataProvider.CreateDefaultProvider(); + var modelMetadata = modelMetadataProvider.GetMetadataForParameter(parameterInfo); + var parameterBinder = ModelBindingTestHelper.GetParameterBinder(modelMetadataProvider); + + var parameter = new ParameterDescriptor() + { + Name = parameterInfo.Name, + ParameterType = parameterInfo.ParameterType, + }; + + var testContext = ModelBindingTestHelper.GetTestContext(request => + { + request.QueryString = new QueryString("?Property1=8"); + }); + + var modelState = testContext.ModelState; + + // Act + var modelBindingResult = await parameterBinder.BindModelAsync(parameter, testContext, modelMetadataProvider, modelMetadata); + + // Assert + Assert.True(modelBindingResult.IsModelSet); + + var model = Assert.IsType(modelBindingResult.Model); + Assert.Equal(8, model.Property1); + + Assert.True(modelState.IsValid); + Assert.Equal(ModelValidationState.Valid, modelState.ValidationState); + + var entry = Assert.Single(modelState, e => e.Key == "Property1").Value; + Assert.Equal("8", entry.AttemptedValue); + Assert.Equal("8", entry.RawValue); + Assert.Equal(ModelValidationState.Valid, entry.ValidationState); + } + + private static void Validation_InifnitelyRecursiveModel_ValidationOnTopLevelParameterMethod([Required] RecursiveModel model) { } + private static void AssertRequiredError(string key, ModelError error) { Assert.Equal(ValidationAttributeUtil.GetRequiredErrorMessage(key), error.ErrorMessage); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs index e43d50af95..53ba4f52fb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs @@ -14,6 +14,7 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.JsonPatch; using Microsoft.AspNetCore.Mvc.ApplicationParts; +using Microsoft.AspNetCore.Mvc.DataAnnotations; using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.Internal; @@ -251,7 +252,8 @@ namespace Microsoft.AspNetCore.Mvc { var excludeFilter = Assert.IsType(provider); Assert.Equal(typeof(XmlNode).FullName, excludeFilter.FullTypeName); - }); + }, + provider => Assert.IsType(provider)); } private static T GetOptions(Action action = null) diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index b4c06b2613..70fea5804a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.DataAnnotations.Internal; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters.Json; using Microsoft.AspNetCore.Mvc.Formatters.Json.Internal; +using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Compilation; @@ -381,14 +382,15 @@ namespace Microsoft.AspNetCore.Mvc typeof(IPostConfigureOptions), new[] { - typeof(MvcOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.Infrastructure.MvcOptionsConfigureCompatibilityOptions", throwOnError: true), + typeof(MvcOptionsConfigureCompatibilityOptions), + typeof(MvcCoreMvcOptionsSetup), } }, { typeof(IPostConfigureOptions), new[] { - typeof(RazorPagesOptions).Assembly.GetType("Microsoft.AspNetCore.Mvc.RazorPages.RazorPagesOptionsConfigureCompatibilityOptions", throwOnError: true), + typeof(RazorPagesOptionsConfigureCompatibilityOptions), } }, { diff --git a/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs b/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs new file mode 100644 index 0000000000..832a5917c6 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs @@ -0,0 +1,16 @@ +// 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 FormatterWebSite.Models; +using Microsoft.AspNetCore.Mvc; + +namespace FormatterWebSite.Controllers +{ + [ApiController] + [Route("[controller]/[action]")] + public class TestApiController : ControllerBase + { + [HttpPost] + public IActionResult PostBookWithNoValidation(BookModelWithNoValidation bookModel) => Ok(); + } +} diff --git a/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs b/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs new file mode 100644 index 0000000000..ad13253e03 --- /dev/null +++ b/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs @@ -0,0 +1,20 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Runtime.Serialization; +using Newtonsoft.Json; + +namespace FormatterWebSite.Models +{ + public class BookModelWithNoValidation + { + public Guid Id { get; set; } + + public string Title { get; set; } + + [JsonRequired] + [DataMember(IsRequired = true)] + public string ISBN { get; set; } + } +} diff --git a/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs b/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs index 847a01b428..49e8ab2e91 100644 --- a/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs +++ b/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs @@ -1,10 +1,14 @@ // 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.ComponentModel.DataAnnotations; +using System.Linq; + namespace FormatterWebSite { // A System.Security.Principal.SecurityIdentifier like type that works on xplat - public class RecursiveIdentifier + public class RecursiveIdentifier : IValidatableObject { public RecursiveIdentifier(string identifier) { @@ -14,5 +18,10 @@ namespace FormatterWebSite public string Value { get; } public RecursiveIdentifier AccountIdentifier => new RecursiveIdentifier(Value); + + public IEnumerable Validate(ValidationContext validationContext) + { + return Enumerable.Empty(); + } } } \ No newline at end of file From c421178a22b6dec3522bad7f5a505ddc5eb82725 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 12 Oct 2018 14:43:54 -0700 Subject: [PATCH 297/316] Update docs for compat switch value --- .../CompatibilityVersion.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs index ad18d058a6..aa63713383 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs @@ -51,10 +51,13 @@ namespace Microsoft.AspNetCore.Mvc /// /// ASP.NET Core MVC 2.1 introduces compatibility switches for the following: /// + /// + /// /// /// /// MvcJsonOptions.AllowInputFormatterExceptionMessages /// RazorPagesOptions.AllowAreas + /// RazorPagesOptions.AllowMappingHeadRequestsToGetHandler /// /// Version_2_1, @@ -66,7 +69,15 @@ namespace Microsoft.AspNetCore.Mvc /// /// ASP.NET Core MVC 2.2 introduces compatibility switches for the following: /// + /// ApiBehaviorOptions.SuppressMapClientErrors + /// ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses /// MvcDataAnnotationsLocalizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes + /// + /// + /// RazorPagesOptions.AllowDefaultHandlingForOptionsRequests + /// RazorViewEngineOptions.AllowRecompilingViewsOnFileChange + /// MvcViewOptions.AllowRenderingMaxLengthAttribute + /// MvcXmlOptions.AllowRfc7807CompliantProblemDetailsFormat /// /// Version_2_2, From 10a94aa7bb44d8537d869484e7b748d1de4156c1 Mon Sep 17 00:00:00 2001 From: "ASP.NET CI" Date: Tue, 16 Oct 2018 12:48:18 -0700 Subject: [PATCH 298/316] Update package branding for 2.2 RTM --- version.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/version.props b/version.props index 7bf6e5246c..0f03397e5d 100644 --- a/version.props +++ b/version.props @@ -1,7 +1,7 @@  2.2.0 - preview3 + rtm t000 a- From 27e75e7a5180c2ca0f4da9d2ef08740ded27cd0b Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 17 Oct 2018 15:47:01 -0700 Subject: [PATCH 299/316] Add a switch to allow turning on ValidationVisitor shortcircuiting (#8599) --- .../CompatibilityVersion.cs | 1 + .../DefaultObjectValidator.cs | 2 +- ...MvcOptionsConfigureCompatibilityOptions.cs | 3 ++ .../Validation/ValidationVisitor.cs | 10 ++++- .../MvcOptions.cs | 41 +++++++++++++++++++ .../Internal/DefaultObjectValidatorTests.cs | 2 +- .../ModelBindingTestHelper.cs | 5 ++- .../CompatibilitySwitchIntegrationTest.cs | 4 ++ 8 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs index aa63713383..d35cdd6110 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs @@ -73,6 +73,7 @@ namespace Microsoft.AspNetCore.Mvc /// ApiBehaviorOptions.SuppressUseValidationProblemDetailsForInvalidModelStateResponses /// MvcDataAnnotationsLocalizationOptions.AllowDataAnnotationsLocalizationForEnumDisplayAttributes /// + /// /// /// RazorPagesOptions.AllowDefaultHandlingForOptionsRequests /// RazorViewEngineOptions.AllowRecompilingViewsOnFileChange diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs b/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs index 8fedb7702b..ff175dc5cf 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Mvc { @@ -46,6 +45,7 @@ namespace Microsoft.AspNetCore.Mvc validationState); visitor.MaxValidationDepth = _mvcOptions.MaxValidationDepth; + visitor.AllowShortCircuitingValidationWhenNoValidatorsArePresent = _mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent; return visitor; } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs index dcc5243fbb..850d24e385 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs @@ -38,6 +38,9 @@ namespace Microsoft.AspNetCore.Mvc.Infrastructure // Matches JsonSerializerSettingsProvider.DefaultMaxDepth values[nameof(MvcOptions.MaxValidationDepth)] = 32; + + values[nameof(MvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent)] = true; + } return values; diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs index e62f825887..0b277c68c6 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs @@ -110,6 +110,12 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation /// public bool ValidateComplexTypesIfChildValidationFails { get; set; } + /// + /// Gets or sets a value that determines if can short circuit validation when a model + /// does not have any associated validators. + /// + public bool AllowShortCircuitingValidationWhenNoValidatorsArePresent { get; set; } + /// /// Validates a object. /// @@ -267,7 +273,9 @@ namespace Microsoft.AspNetCore.Mvc.ModelBinding.Validation } // If the metadata indicates that no validators exist AND the aggregate state for the key says that the model graph // is not invalid (i.e. is one of Unvalidated, Valid, or Skipped) we can safely mark the graph as valid. - else if (metadata.HasValidators == false && + else if ( + AllowShortCircuitingValidationWhenNoValidatorsArePresent && + metadata.HasValidators == false && ModelState.GetFieldValidationState(key) != ModelValidationState.Invalid) { // No validators will be created for this graph of objects. Mark it as valid if it wasn't previously validated. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs index dd8e716d18..a97c9a965e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs @@ -32,6 +32,7 @@ namespace Microsoft.AspNetCore.Mvc private readonly CompatibilitySwitch _suppressBindingUndefinedValueToEnumType; private readonly CompatibilitySwitch _enableEndpointRouting; private readonly NullableCompatibilitySwitch _maxValidationDepth; + private readonly CompatibilitySwitch _allowShortCircuitingValidationWhenNoValidatorsArePresent; private readonly ICompatibilitySwitch[] _switches; /// @@ -58,6 +59,7 @@ namespace Microsoft.AspNetCore.Mvc _suppressBindingUndefinedValueToEnumType = new CompatibilitySwitch(nameof(SuppressBindingUndefinedValueToEnumType)); _enableEndpointRouting = new CompatibilitySwitch(nameof(EnableEndpointRouting)); _maxValidationDepth = new NullableCompatibilitySwitch(nameof(MaxValidationDepth)); + _allowShortCircuitingValidationWhenNoValidatorsArePresent = new CompatibilitySwitch(nameof(AllowShortCircuitingValidationWhenNoValidatorsArePresent)); _switches = new ICompatibilitySwitch[] { @@ -68,6 +70,7 @@ namespace Microsoft.AspNetCore.Mvc _suppressBindingUndefinedValueToEnumType, _enableEndpointRouting, _maxValidationDepth, + _allowShortCircuitingValidationWhenNoValidatorsArePresent, }; } @@ -442,6 +445,44 @@ namespace Microsoft.AspNetCore.Mvc } } + /// + /// Gets or sets a value that determines if + /// can short-circuit validation when a model does not have any associated validators. + /// + /// + /// The default value is if the version is + /// or later; otherwise. + /// + /// + /// When is , that is, it is determined + /// that a model or any of it's properties or collection elements cannot have any validators, + /// can short-circuit validation for the model and mark the object + /// graph as valid. Setting this property to , allows to + /// perform this optimization. + /// + /// This property is associated with a compatibility switch and can provide a different behavior depending on + /// the configured compatibility version for the application. See for + /// guidance and examples of setting the application's compatibility version. + /// + /// + /// Configuring the desired value of the compatibility switch by calling this property's setter will take precedence + /// over the value implied by the application's . + /// + /// + /// If the application's compatibility version is set to then + /// this setting will have the value unless explicitly configured. + /// + /// + /// If the application's compatibility version is set to or + /// earlier then this setting will have the value unless explicitly configured. + /// + /// + public bool AllowShortCircuitingValidationWhenNoValidatorsArePresent + { + get => _allowShortCircuitingValidationWhenNoValidatorsArePresent.Value; + set => _allowShortCircuitingValidationWhenNoValidatorsArePresent.Value = value; + } + IEnumerator IEnumerable.GetEnumerator() { return ((IEnumerable)_switches).GetEnumerator(); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs index 8fad61aadd..83cf64890a 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class DefaultObjectValidatorTests { - private readonly MvcOptions _options = new MvcOptions(); + private readonly MvcOptions _options = new MvcOptions { AllowShortCircuitingValidationWhenNoValidatorsArePresent = true }; private ModelMetadataProvider MetadataProvider { get; } = TestModelMetadataProvider.CreateDefaultProvider(); diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs index 91a290406a..3a882567d0 100644 --- a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs +++ b/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests return new DefaultObjectValidator( metadataProvider, GetModelValidatorProviders(options), - options?.Value ?? new MvcOptions()); + options?.Value ?? new MvcOptions { AllowShortCircuitingValidationWhenNoValidatorsArePresent = true }); } private static IList GetModelValidatorProviders(IOptions options) @@ -197,7 +197,8 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTests serviceCollection .AddSingleton() .AddSingleton(NullLoggerFactory.Instance) - .AddTransient, Logger>(); + .AddTransient, Logger>() + .Configure(options => options.AllowShortCircuitingValidationWhenNoValidatorsArePresent = true); if (updateOptions != null) { diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs index ea544c1d26..e4020042f3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs @@ -56,6 +56,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); + Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); } [Fact] @@ -93,6 +94,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.True(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.False(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.False(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); + Assert.False(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); } [Fact] @@ -130,6 +132,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); + Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); } [Fact] @@ -167,6 +170,7 @@ namespace Microsoft.AspNetCore.Mvc.IntegrationTest Assert.False(razorViewEngineOptions.AllowRecompilingViewsOnFileChange); Assert.True(razorPagesOptions.AllowDefaultHandlingForOptionsRequests); Assert.True(xmlOptions.AllowRfc7807CompliantProblemDetailsFormat); + Assert.True(mvcOptions.AllowShortCircuitingValidationWhenNoValidatorsArePresent); } // This just does the minimum needed to be able to resolve these options. From 8a183bb4f461994aaa66bd04f71be8f48023c432 Mon Sep 17 00:00:00 2001 From: Pranav K Date: Thu, 18 Oct 2018 10:15:03 -0700 Subject: [PATCH 300/316] Allow serving Razor files with leading underscore Fixes https://github.com/aspnet/Mvc/issues/8617 --- .../RazorProjectPageRouteModelProvider.cs | 16 ------ .../CompiledPageRouteModelProviderTest.cs | 52 ++++++++++++++++++- .../RazorProjectPageRouteModelProviderTest.cs | 46 +++++++--------- 3 files changed, 71 insertions(+), 43 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs index ad8187cb78..23384fa9f7 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs @@ -57,11 +57,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { foreach (var item in _razorFileSystem.EnumerateItems(_pagesOptions.RootDirectory)) { - if (!IsRouteable(item)) - { - continue; - } - var relativePath = item.CombinedPath; if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase))) { @@ -99,11 +94,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { foreach (var item in _razorFileSystem.EnumerateItems(AreaRootDirectory)) { - if (!IsRouteable(item)) - { - continue; - } - var relativePath = item.CombinedPath; if (context.RouteModels.Any(m => string.Equals(relativePath, m.RelativePath, StringComparison.OrdinalIgnoreCase))) { @@ -125,11 +115,5 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal } } } - - private static bool IsRouteable(RazorProjectItem item) - { - // Pages like _ViewImports should not be routeable. - return !item.FileName.StartsWith("_", StringComparison.OrdinalIgnoreCase); - } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs index 1e2ced5c55..0ddf9f3af4 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.AspNetCore.Mvc.ApplicationModels; using Microsoft.AspNetCore.Mvc.ApplicationParts; -using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.Razor.Compilation; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Razor.Hosting; @@ -563,6 +562,57 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }); } + [Fact] + public void OnProvidersExecuting_AllowsRazorFilesWithUnderscorePrefix() + { + // Arrange + var descriptors = new[] + { + CreateVersion_2_1_Descriptor("/Pages/_About.cshtml"), + CreateVersion_2_1_Descriptor("/Pages/Home.cshtml"), + }; + + var provider = CreateProvider(descriptors: descriptors); + var context = new PageRouteModelProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection( + context.RouteModels, + result => + { + Assert.Equal("/Pages/_About.cshtml", result.RelativePath); + Assert.Equal("/_About", result.ViewEnginePath); + Assert.Collection( + result.Selectors, + selector => Assert.Equal("_About", selector.AttributeRouteModel.Template)); + Assert.Collection( + result.RouteValues.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("page", kvp.Key); + Assert.Equal("/_About", kvp.Value); + }); + }, + result => + { + Assert.Equal("/Pages/Home.cshtml", result.RelativePath); + Assert.Equal("/Home", result.ViewEnginePath); + Assert.Collection( + result.Selectors, + selector => Assert.Equal("Home", selector.AttributeRouteModel.Template)); + Assert.Collection( + result.RouteValues.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("page", kvp.Key); + Assert.Equal("/Home", kvp.Value); + }); + }); + } + [Fact] public void GetRouteTemplate_ReturnsPathFromRazorPageAttribute() { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs index e6bc51da23..4b8aa8e581 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs @@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Manage/Categories.cshtml", "@page")); fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/Index.cshtml", "@page")); fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/List.cshtml", "@page \"{sortOrder?}\"")); - fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_ViewStart.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Areas/Products/Pages/_Test.cshtml", "@page")); var optionsManager = Options.Create(new RazorPagesOptions { AllowAreas = true }); var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance); @@ -102,6 +102,24 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }); }, model => + { + Assert.Equal("/Areas/Products/Pages/_Test.cshtml", model.RelativePath); + Assert.Equal("/_Test", model.ViewEnginePath); + Assert.Collection(model.Selectors, + selector => Assert.Equal("Products/_Test", selector.AttributeRouteModel.Template)); + Assert.Collection(model.RouteValues.OrderBy(k => k.Key), + kvp => + { + Assert.Equal("area", kvp.Key); + Assert.Equal("Products", kvp.Value); + }, + kvp => + { + Assert.Equal("page", kvp.Key); + Assert.Equal("/_Test", kvp.Value); + }); + }, + model => { Assert.Equal("/Areas/Products/Pages/Manage/Categories.cshtml", model.RelativePath); Assert.Equal("/Manage/Categories", model.ViewEnginePath); @@ -272,37 +290,13 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal }); } - [Fact] - public void OnProvidersExecuting_SkipsPagesStartingWithUnderscore() - { - // Arrange - var fileSystem = new VirtualRazorProjectFileSystem(); - fileSystem.Add(new TestRazorProjectItem("/Pages/Home.cshtml", "@page")); - fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", "@page")); - - var optionsManager = Options.Create(new RazorPagesOptions()); - optionsManager.Value.RootDirectory = "/"; - var provider = new RazorProjectPageRouteModelProvider(fileSystem, optionsManager, NullLoggerFactory.Instance); - var context = new PageRouteModelProviderContext(); - - // Act - provider.OnProvidersExecuting(context); - - // Assert - Assert.Collection(context.RouteModels, - model => - { - Assert.Equal("/Pages/Home.cshtml", model.RelativePath); - }); - } - [Fact] public void OnProvidersExecuting_DiscoversFilesUnderBasePath() { // Arrange var fileSystem = new VirtualRazorProjectFileSystem(); fileSystem.Add(new TestRazorProjectItem("/Pages/Index.cshtml", "@page")); - fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", "@page")); + fileSystem.Add(new TestRazorProjectItem("/Pages/_Layout.cshtml", "")); fileSystem.Add(new TestRazorProjectItem("/NotPages/Index.cshtml", "@page")); fileSystem.Add(new TestRazorProjectItem("/NotPages/_Layout.cshtml", "@page")); fileSystem.Add(new TestRazorProjectItem("/Index.cshtml", "@page")); From ddbe0fef2617f94a42312f002860f8f45145f11d Mon Sep 17 00:00:00 2001 From: Patrick Westerhoff Date: Thu, 18 Oct 2018 01:18:45 +0200 Subject: [PATCH 301/316] Allow custom handling of antiforgery failures To enable custom handling of antiforgery validation failures, use an `AntiforgeryValidationFailedResult` which is just a `BadRequestResult` but allows to be identified explicitly inside always-running result filters using the `IAntiforgeryValidationFailedResult` marker interface. --- .../AntiforgeryValidationFailedResult.cs | 15 ++++++++++ .../IAntiforgeryValidationFailedResult.cs | 13 +++++++++ ...dateAntiforgeryTokenAuthorizationFilter.cs | 2 +- .../AntiforgeryTests.cs | 28 ++++++++++++++++++- ...AntiforgeryTokenAuthorizationFilterTest.cs | 24 ++++++++++++++++ .../Controllers/AntiforgeryController.cs | 11 ++++++++ ...AntiforgeryValidationFailedResultFilter.cs | 20 +++++++++++++ 7 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs create mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs create mode 100644 test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs new file mode 100644 index 0000000000..95e0643127 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs @@ -0,0 +1,15 @@ +// 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.Core.Infrastructure; + +namespace Microsoft.AspNetCore.Mvc +{ + /// + /// A used for antiforgery validation + /// failures. Use to + /// match for validation failures inside MVC result filters. + /// + public class AntiforgeryValidationFailedResult : BadRequestResult, IAntiforgeryValidationFailedResult + { } +} diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs new file mode 100644 index 0000000000..07befb4452 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.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.Core.Infrastructure +{ + /// + /// Represents an that is used when the + /// antiforgery validation failed. This can be matched inside MVC result + /// filters to process the validation failure. + /// + public interface IAntiforgeryValidationFailedResult : IActionResult + { } +} \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs index 7225eb76c1..901ea36cf8 100644 --- a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs +++ b/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs @@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal catch (AntiforgeryValidationException exception) { _logger.AntiforgeryTokenInvalid(exception.Message, exception); - context.Result = new BadRequestResult(); + context.Result = new AntiforgeryValidationFailedResult(); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs index 78fc00bec6..746f949205 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Net; using System.Net.Http; using System.Threading.Tasks; -using Microsoft.AspNetCore.Antiforgery; using Xunit; namespace Microsoft.AspNetCore.Mvc.FunctionalTests @@ -175,5 +174,32 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests var pragmaValue = Assert.Single(response.Headers.Pragma.ToArray()); Assert.Equal("no-cache", pragmaValue.Name); } + + [Fact] + public async Task RequestWithoutAntiforgeryToken_SendsBadRequest() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Antiforgery/Login"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); + } + + [Fact] + public async Task RequestWithoutAntiforgeryToken_ExecutesResultFilter() + { + // Arrange + var request = new HttpRequestMessage(HttpMethod.Post, "http://localhost/Antiforgery/LoginWithRedirectResultFilter"); + + // Act + var response = await Client.SendAsync(request); + + // Assert + Assert.Equal(HttpStatusCode.Redirect, response.StatusCode); + Assert.Equal("http://example.com/antiforgery-redirect", response.Headers.Location.AbsoluteUri); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs index 2748716667..d8e62d278e 100644 --- a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs @@ -73,5 +73,29 @@ namespace Microsoft.AspNetCore.Mvc.ViewFeatures.Internal // Assert antiforgery.Verify(a => a.ValidateRequestAsync(It.IsAny()), Times.Never()); } + + [Fact] + public async Task Filter_SetsFailureResult() + { + // Arrange + var antiforgery = new Mock(MockBehavior.Strict); + antiforgery + .Setup(a => a.ValidateRequestAsync(It.IsAny())) + .Throws(new AntiforgeryValidationException("Failed")) + .Verifiable(); + + var filter = new ValidateAntiforgeryTokenAuthorizationFilter(antiforgery.Object, NullLoggerFactory.Instance); + + var actionContext = new ActionContext(new DefaultHttpContext(), new RouteData(), new ActionDescriptor()); + actionContext.HttpContext.Request.Method = "POST"; + + var context = new AuthorizationFilterContext(actionContext, new[] { filter }); + + // Act + await filter.OnAuthorizationAsync(context); + + // Assert + Assert.IsType(context.Result); + } } } diff --git a/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs b/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs index 7da1b783c2..415a34ba46 100644 --- a/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs +++ b/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs @@ -1,6 +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 BasicWebSite.Filters; using BasicWebSite.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; @@ -39,6 +40,16 @@ namespace BasicWebSite.Controllers return "OK"; } + // POST: /Antiforgery/LoginWithRedirectResultFilter + [HttpPost] + [AllowAnonymous] + [ValidateAntiForgeryToken] + [TypeFilter(typeof(RedirectAntiforgeryValidationFailedResultFilter))] + public string LoginWithRedirectResultFilter(LoginViewModel model) + { + return "Ok"; + } + // GET: /Antiforgery/FlushAsyncLogin [AllowAnonymous] public ActionResult FlushAsyncLogin(string returnUrl = null) diff --git a/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs b/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs new file mode 100644 index 0000000000..ca7c61781b --- /dev/null +++ b/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Core.Infrastructure; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace BasicWebSite.Filters +{ + public class RedirectAntiforgeryValidationFailedResultFilter : IAlwaysRunResultFilter + { + public void OnResultExecuting(ResultExecutingContext context) + { + if (context.Result is IAntiforgeryValidationFailedResult result) + { + context.Result = new RedirectResult("http://example.com/antiforgery-redirect"); + } + } + + public void OnResultExecuted(ResultExecutedContext context) + { } + } +} From 76a30b0911db8cc28371653582a7d485ba12b080 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 19 Oct 2018 17:39:11 -0700 Subject: [PATCH 302/316] Remove LinkGenerationTemplate This doesn't really accomplish our goals for 2.2 - I don't have a clear scenario where I would tell a developer to use this VS something else. Will reevaluate in 3.0 --- .../ControllerLinkGeneratorExtensions.cs | 40 ------------------- .../Routing/PageLinkGeneratorExtensions.cs | 35 ---------------- .../ControllerLinkGeneratorExtensionsTest.cs | 21 ---------- .../PageLinkGeneratorExtensionsTest.cs | 23 ----------- 4 files changed, 119 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs index 01481bf488..fb02fae6f4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs @@ -13,11 +13,6 @@ namespace Microsoft.AspNetCore.Routing /// public static class ControllerLinkGeneratorExtensions { - private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() - { - UseAmbientValues = true, - }; - /// /// Generates a URI with an absolute path based on the provided values. /// @@ -224,41 +219,6 @@ namespace Microsoft.AspNetCore.Routing return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); } - /// - /// Gets a based on the provided , , and . - /// - /// The . - /// The action name. Used to resolve endpoints. - /// The controller name. Used to resolve endpoints. - /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. - /// - /// A if one or more endpoints matching the address can be found, otherwise null. - /// - public static LinkGenerationTemplate GetTemplateByAction( - this LinkGenerator generator, - string action, - string controller, - object values = default) - { - if (generator == null) - { - throw new ArgumentNullException(nameof(generator)); - } - - if (action == null) - { - throw new ArgumentNullException(nameof(action)); - } - - if (controller == null) - { - throw new ArgumentNullException(nameof(controller)); - } - - var address = CreateAddress(httpContext: null, action, controller, values); - return generator.GetTemplateByAddress(address, _templateOptions); - } - private static RouteValuesAddress CreateAddress(HttpContext httpContext, string action, string controller, object values) { var explicitValues = new RouteValueDictionary(values); diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs index ec508a9645..a7e9cde9c0 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs @@ -13,11 +13,6 @@ namespace Microsoft.AspNetCore.Routing /// public static class PageLinkGeneratorExtensions { - private static readonly LinkGenerationTemplateOptions _templateOptions = new LinkGenerationTemplateOptions() - { - UseAmbientValues = true, - }; - /// /// Generates a URI with an absolute path based on the provided values. /// @@ -216,36 +211,6 @@ namespace Microsoft.AspNetCore.Routing return generator.GetUriByAddress(address, address.ExplicitValues, scheme, host, pathBase, fragment, options); } - /// - /// Gets a based on the provided , , and . - /// - /// The . - /// The page name. Used to resolve endpoints. - /// The page handler name. Optional. - /// The route values. Optional. Used to resolve endpoints and expand parameters in the route template. - /// - /// A if one or more endpoints matching the address can be found, otherwise null. - /// - public static LinkGenerationTemplate GetTemplateByPage( - this LinkGenerator generator, - string page, - string handler = default, - object values = default) - { - if (generator == null) - { - throw new ArgumentNullException(nameof(generator)); - } - - if (page == null) - { - throw new ArgumentNullException(nameof(page)); - } - - var address = CreateAddress(httpContext: null, page, handler, values); - return generator.GetTemplateByAddress(address, _templateOptions); - } - private static RouteValuesAddress CreateAddress(HttpContext httpContext, string page, string handler, object values) { var explicitValues = new RouteValueDictionary(values); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs index 29a197d67f..a07cad51cb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs @@ -168,27 +168,6 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Home/Index/?query=some%3Fquery#Fragment?", uri); } - [Fact] - public void GetTemplateByAction_CreatesTemplate() - { - // Arrange - var endpoint1 = CreateEndpoint( - "Home/Index/{id}", - metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); - var endpoint2 = CreateEndpoint( - "Home/Index/{id?}", - metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { controller = "Home", action = "Index", })) }); - - var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - - // Act - var template = linkGenerator.GetTemplateByAction(action: "Index", controller: "Home"); - - // Assert - Assert.NotNull(template); - Assert.Equal("/Home/Index/17", template.GetPath(new { id = 17 })); - } - private RouteEndpoint CreateEndpoint( string template, object defaults = null, diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs index 89531042f1..3fc85649c6 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs @@ -166,29 +166,6 @@ namespace Microsoft.AspNetCore.Routing Assert.Equal("http://example.com/Foo/Bar%3Fencodeme%3F/Admin/ManageUsers/?query=some%3Fquery#Fragment?", uri); } - [Fact] - public void GetTemplateByAction_CreatesTemplate() - { - // Arrange - var endpoint1 = CreateEndpoint( - "About/{id}", - defaults: new { page = "/About", }, - metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/About", })) }); - var endpoint2 = CreateEndpoint( - "Admin/ManageUsers", - defaults: new { page = "/Admin/ManageUsers", }, - metadata: new[] { new RouteValuesAddressMetadata(routeName: null, new RouteValueDictionary(new { page = "/Admin/ManageUsers", })) }); - - var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - - // Act - var template = linkGenerator.GetTemplateByPage(page: "/About"); - - // Assert - Assert.NotNull(template); - Assert.Equal("/About/17", template.GetPath(new { id = 17 })); - } - private RouteEndpoint CreateEndpoint( string template, object defaults = null, From 40959a97e78488133824807c683d16301eab4c79 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 23 Oct 2018 16:10:43 +1300 Subject: [PATCH 303/316] Fix link generation of routes with default values (#8616) --- build/dependencies.props | 150 ++--- .../Internal/MvcEndpointDataSource.cs | 217 +++++-- .../Internal/RoutePatternWriter.cs | 4 + .../Internal/MvcEndpointDataSourceTests.cs | 595 +++++++++++++++++- .../RoutingTestsBase.cs | 130 ++++ .../Controllers/DefaultValuesController.cs | 34 + test/WebSites/RoutingWebSite/Startup.cs | 12 + .../RoutingWebSite/StartupWith21Compat.cs | 17 +- 8 files changed, 985 insertions(+), 174 deletions(-) create mode 100644 test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs diff --git a/build/dependencies.props b/build/dependencies.props index 84f3631ef7..c0129c246e 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -7,7 +7,7 @@ is not otherwise referenced. They avoid unnecessary changes to the Universe build graph or to product dependencies. Do not use these properties elsewhere. --> - + 0.9.9 0.10.13 2.1.1 @@ -16,90 +16,90 @@ 0.43.0 2.1.1.1 2.1.1 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-a-preview3-22cors-16556 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 2.0.0 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-a-preview3-address-scheme-17059 - 2.2.0-a-preview3-address-scheme-17059 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-a-rtm-allow-required-parameters-17081 + 2.2.0-a-rtm-allow-required-parameters-17081 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 5.2.6 15.6.82 2.8.0 2.8.0 - 2.2.0-preview3-35359 + 2.2.0-rtm-35519 1.7.0 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 2.1.0 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 2.0.9 2.1.3 - 2.2.0-preview3-26927-02 - 2.2.0-preview3-35359 - 2.2.0-preview3-35359 + 2.2.0-preview3-27014-02 + 2.2.0-rtm-35519 + 2.2.0-rtm-35519 15.6.1 - 4.7.49 + 4.10.0 2.0.3 1.0.1 11.0.2 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index d371d02f79..bf8df39064 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -222,26 +222,63 @@ namespace Microsoft.AspNetCore.Mvc.Internal bool suppressPathMatching) { var newPathSegments = routePattern.PathSegments.ToList(); + var hasLinkGenerationEndpoint = false; + + // Create a mutable copy + var nonInlineDefaultsCopy = nonInlineDefaults != null + ? new RouteValueDictionary(nonInlineDefaults) + : null; + + var resolvedRouteValues = ResolveActionRouteValues(action, allDefaults); for (var i = 0; i < newPathSegments.Count; i++) { // Check if the pattern can be shortened because the remaining parameters are optional // - // e.g. Matching pattern {controller=Home}/{action=Index}/{id?} against HomeController.Index - // can resolve to the following endpoints: - // - /Home/Index/{id?} - // - /Home + // e.g. Matching pattern {controller=Home}/{action=Index} against HomeController.Index + // can resolve to the following endpoints: (sorted by RouteEndpoint.Order) // - / - if (UseDefaultValuePlusRemainingSegmentsOptional(i, action, allDefaults, newPathSegments)) + // - /Home + // - /Home/Index + if (UseDefaultValuePlusRemainingSegmentsOptional( + i, + action, + resolvedRouteValues, + allDefaults, + ref nonInlineDefaultsCopy, + newPathSegments)) { + // The route pattern has matching default values AND an optional parameter + // For link generation we need to include an endpoint with parameters and default values + // so the link is correctly shortened + // e.g. {controller=Home}/{action=Index}/{id=17} + if (!hasLinkGenerationEndpoint) + { + var ep = CreateEndpoint( + action, + resolvedRouteValues, + name, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, + nonInlineDefaultsCopy, + routeOrder++, + dataTokens, + suppressLinkGeneration, + true); + endpoints.Add(ep); + + hasLinkGenerationEndpoint = true; + } + var subPathSegments = newPathSegments.Take(i); var subEndpoint = CreateEndpoint( action, + resolvedRouteValues, name, GetPattern(ref patternStringBuilder, subPathSegments), subPathSegments, - nonInlineDefaults, + nonInlineDefaultsCopy, routeOrder++, dataTokens, suppressLinkGeneration, @@ -249,15 +286,83 @@ namespace Microsoft.AspNetCore.Mvc.Internal endpoints.Add(subEndpoint); } - List segmentParts = null; // Initialize only as needed - var segment = newPathSegments[i]; - for (var j = 0; j < segment.Parts.Count; j++) - { - var part = segment.Parts[j]; + UpdatePathSegments(i, action, resolvedRouteValues, routePattern, newPathSegments, ref allParameterPolicies); + } - if (part.IsParameter && - part is RoutePatternParameterPart parameterPart && - action.RouteValues.ContainsKey(parameterPart.Name)) + var finalEndpoint = CreateEndpoint( + action, + resolvedRouteValues, + name, + GetPattern(ref patternStringBuilder, newPathSegments), + newPathSegments, + nonInlineDefaultsCopy, + routeOrder++, + dataTokens, + suppressLinkGeneration, + suppressPathMatching); + endpoints.Add(finalEndpoint); + + return routeOrder; + + string GetPattern(ref StringBuilder sb, IEnumerable segments) + { + if (sb == null) + { + sb = new StringBuilder(); + } + + RoutePatternWriter.WriteString(sb, segments); + var rawPattern = sb.ToString(); + sb.Length = 0; + + return rawPattern; + } + } + + private static IDictionary ResolveActionRouteValues(ActionDescriptor action, IReadOnlyDictionary allDefaults) + { + Dictionary resolvedRequiredValues = null; + + foreach (var kvp in action.RouteValues) + { + // Check whether there is a matching default value with a different case + // e.g. {controller=HOME}/{action} with HomeController.Index will have route values: + // - controller = HOME + // - action = Index + if (allDefaults.TryGetValue(kvp.Key, out var value) && + value is string defaultValue && + !string.Equals(kvp.Value, defaultValue, StringComparison.Ordinal) && + string.Equals(kvp.Value, defaultValue, StringComparison.OrdinalIgnoreCase)) + { + if (resolvedRequiredValues == null) + { + resolvedRequiredValues = new Dictionary(action.RouteValues, StringComparer.OrdinalIgnoreCase); + } + + resolvedRequiredValues[kvp.Key] = defaultValue; + } + } + + return resolvedRequiredValues ?? action.RouteValues; + } + + private void UpdatePathSegments( + int i, + ActionDescriptor action, + IDictionary resolvedRequiredValues, + RoutePattern routePattern, + List newPathSegments, + ref IDictionary> allParameterPolicies) + { + List segmentParts = null; // Initialize only as needed + var segment = newPathSegments[i]; + for (var j = 0; j < segment.Parts.Count; j++) + { + var part = segment.Parts[j]; + + if (part is RoutePatternParameterPart parameterPart) + { + if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var parameterRouteValue)) { if (segmentParts == null) { @@ -268,9 +373,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal allParameterPolicies = MvcEndpointInfo.BuildParameterPolicies(routePattern.Parameters, _parameterPolicyFactory); } - // Replace parameter with literal value - var parameterRouteValue = action.RouteValues[parameterPart.Name]; - // Route value could be null if it is a "known" route value. // Do not use the null value to de-normalize the route pattern, // instead leave the parameter unchanged. @@ -297,47 +399,21 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } } - - // A parameter part was replaced so replace segment with updated parts - if (segmentParts != null) - { - newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); - } } - var endpoint = CreateEndpoint( - action, - name, - GetPattern(ref patternStringBuilder, newPathSegments), - newPathSegments, - nonInlineDefaults, - routeOrder++, - dataTokens, - suppressLinkGeneration, - suppressPathMatching); - endpoints.Add(endpoint); - - return routeOrder; - - string GetPattern(ref StringBuilder sb, IEnumerable segments) + // A parameter part was replaced so replace segment with updated parts + if (segmentParts != null) { - if (sb == null) - { - sb = new StringBuilder(); - } - - RoutePatternWriter.WriteString(sb, segments); - var rawPattern = sb.ToString(); - sb.Length = 0; - - return rawPattern; + newPathSegments[i] = RoutePatternFactory.Segment(segmentParts); } } private bool UseDefaultValuePlusRemainingSegmentsOptional( int segmentIndex, ActionDescriptor action, + IDictionary resolvedRequiredValues, IReadOnlyDictionary allDefaults, + ref RouteValueDictionary nonInlineDefaults, List pathSegments) { // Check whether the remaining segments are all optional and one or more of them is @@ -352,22 +428,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal var part = segment.Parts[j]; if (part.IsParameter && part is RoutePatternParameterPart parameterPart) { - if (parameterPart.IsOptional || parameterPart.IsCatchAll) + if (allDefaults.TryGetValue(parameterPart.Name, out var v)) { - continue; - } - - if (action.RouteValues.ContainsKey(parameterPart.Name)) - { - if (allDefaults.TryGetValue(parameterPart.Name, out var v) - && v is string defaultValue - && action.RouteValues.TryGetValue(parameterPart.Name, out var routeValue) - && string.Equals(defaultValue, routeValue, StringComparison.OrdinalIgnoreCase)) + if (resolvedRequiredValues.TryGetValue(parameterPart.Name, out var routeValue)) { + if (string.Equals(v as string, routeValue, StringComparison.OrdinalIgnoreCase)) + { + usedDefaultValue = true; + continue; + } + } + else + { + if (nonInlineDefaults == null) + { + nonInlineDefaults = new RouteValueDictionary(); + } + nonInlineDefaults.TryAdd(parameterPart.Name, v); + usedDefaultValue = true; continue; } } + + if (parameterPart.IsOptional || parameterPart.IsCatchAll) + { + continue; + } } else if (part.IsSeparator && part is RoutePatternSeparatorPart separatorPart && separatorPart.Content == ".") @@ -441,6 +528,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal private RouteEndpoint CreateEndpoint( ActionDescriptor action, + IDictionary actionRouteValues, string routeName, string patternRawText, IEnumerable segments, @@ -461,12 +549,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal }; var defaults = new RouteValueDictionary(nonInlineDefaults); - EnsureRequiredValuesInDefaults(action.RouteValues, defaults); + EnsureRequiredValuesInDefaults(actionRouteValues, defaults, segments); var metadataCollection = BuildEndpointMetadata( action, routeName, - new RouteValueDictionary(action.RouteValues), + new RouteValueDictionary(actionRouteValues), dataTokens, suppressLinkGeneration, suppressPathMatching); @@ -554,7 +642,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal return metadataCollection; } - // Ensure required values are a subset of defaults + // Ensure route values are a subset of defaults // Examples: // // Template: {controller}/{action}/{category}/{id?} @@ -568,9 +656,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Required values: controller=foo, action=bar // Final constructed pattern: foo/bar/{category}/{id?} // Final defaults: controller=foo, action=bar, category=products - private void EnsureRequiredValuesInDefaults(IDictionary requiredValues, RouteValueDictionary defaults) + private void EnsureRequiredValuesInDefaults( + IDictionary routeValues, + RouteValueDictionary defaults, + IEnumerable segments) { - foreach (var kvp in requiredValues) + foreach (var kvp in routeValues) { if (kvp.Value != null) { diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs index 5beeb6baa6..3f0e0f6445 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs @@ -40,6 +40,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (parameterPart.IsCatchAll) { sb.Append("*"); + if (!parameterPart.EncodeSlashes) + { + sb.Append("*"); + } } sb.Append(parameterPart.Name); foreach (var item in parameterPart.ParameterPolicies) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 24622f85cd..618b281eba 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -146,22 +146,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal private static TheoryData GetSingleActionData(bool isConventionalRouting) { - var data = new TheoryData + var data = new TheoryData { - {"{controller}/{action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, - {"{controller}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }}, - {"{action}/{id?}", isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }}, - {"{Controller}/{Action}/{id?}", new[] { "TestController/TestAction/{id?}" }}, - {"{CONTROLLER}/{ACTION}/{id?}", new[] { "TestController/TestAction/{id?}" }}, - {"{controller}/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }}, - {"{controller}/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }}, - {"{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }}, - {"{controller}/{action}/{*catchAll}", new[] { "TestController/TestAction/{*catchAll}" }}, - {"{controller}/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }}, - {"{controller}/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }}, - {"{controller}/{action}.{ext?}", new[] { "TestController/TestAction.{ext?}" }}, - {"{controller}/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }}, - {"{controller:upper-case}/{action=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TestAction.{ext?}" }}, + {"{controller}/{action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }}, + {"{controller}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestController/{id?}" }}, + {"{action}/{id?}", null, isConventionalRouting ? new string[] { } : new[] { "TestAction/{id?}" }}, + {"{Controller}/{Action}/{id?}", null, new[] { "TestController/TestAction/{id?}" }}, + {"{Controller}/{Action}/{id?}/{more?}", null, new[] { "TestController/TestAction/{id?}/{more?}" }}, + {"{CONTROLLER}/{ACTION}/{id?}", null, new[] { "TestController/TestAction/{id?}" }}, + {"{controller}/{action=TestAction}", "TestController/{action=TestAction}", new[] { "TestController", "TestController/TestAction" }}, + {"{controller}/{action=TestAction}/{id?}", "TestController/{action=TestAction}/{id?}", new[] { "TestController", "TestController/TestAction/{id?}" }}, + {"{controller}/{action=TESTACTION}/{id?}", "TestController/{action=TESTACTION}/{id?}", new[] { "TestController", "TestController/TESTACTION/{id?}" }}, + {"{controller}/{action=TestAction}/{id?}/{more}", null, new[] { "TestController/TestAction/{id?}/{more}" }}, + {"{controller=TestController}/{action=TestAction}/{id?}", "{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestController", "TestController/TestAction/{id?}" }}, + {"{controller=TestController}/{action=TestAction}/{id?}/{more?}", "{controller=TestController}/{action=TestAction}/{id?}/{more?}", new[] { "", "TestController", "TestController/TestAction/{id?}/{more?}" }}, + {"{controller}/{action}/{*catchAll}", null, new[] { "TestController/TestAction/{*catchAll}" }}, + {"{controller}/{action=TestAction}/{*catchAll}", "TestController/{action=TestAction}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{*catchAll}" }}, + {"{controller}/{action=TestAction}/{id?}/{*catchAll}", "TestController/{action=TestAction}/{id?}/{*catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{*catchAll}" }}, + {"{controller}/{action=TestAction}/{id?}/{**catchAll}", "TestController/{action=TestAction}/{id?}/{**catchAll}", new[] { "TestController", "TestController/TestAction/{id?}/{**catchAll}" }}, + {"{controller}/{action}.{ext?}", null, new[] { "TestController/TestAction.{ext?}" }}, + {"{controller}/{action=TestAction}.{ext?}", "TestController/{action=TestAction}.{ext?}", new[] { "TestController", "TestController/TestAction.{ext?}" }}, + {"{controller}/{action=TestAction}.{ext?}/{more?}", "TestController/{action=TestAction}.{ext?}/{more?}", new[] { "TestController", "TestController/TestAction.{ext?}/{more?}" }}, + {"{controller}/{action=TestAction}.{ext?}/{more}", null, new[] { "TestController/TestAction.{ext?}/{more}" }}, + {"{controller:upper-case}/{action:upper-case=TestAction}.{ext?}", "TESTCONTROLLER/{action:upper-case=TestAction}.{ext?}", new[] { "TESTCONTROLLER", "TESTCONTROLLER/TESTACTION.{ext?}" }}, }; return data; @@ -169,7 +176,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [MemberData(nameof(GetSingleActionData_Conventional))] - public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) + public void Endpoints_Conventional_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -178,9 +185,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute)); // Act - var endpoints = dataSource.Endpoints; + var endpoints = dataSource.Endpoints.ToList(); // Assert + + // Ensure there are no endpoints with duplicate Order values + Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1); + + endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList(); + + AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints); + var inspectors = finalEndpointPatterns .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); @@ -191,7 +206,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal [Theory] [MemberData(nameof(GetSingleActionData_Attribute))] - public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string[] finalEndpointPatterns) + public void Endpoints_AttributeRouting_SingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointPatterns) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -200,7 +215,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); // Act - var endpoints = dataSource.Endpoints; + var endpoints = dataSource.Endpoints.ToList(); + + // Ensure there are no endpoints with duplicate Order values + Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1); + + endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList(); + + AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints); // Assert var inspectors = finalEndpointPatterns @@ -212,14 +234,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal } [Theory] - [InlineData("{area}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] - [InlineData("{controller}/{action}/{id?}", new string[] { })] - [InlineData("{area=TestArea}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] - [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] - [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] - [InlineData("{area:exists}/{controller}/{action}/{id?}", new[] { "TestArea/TestController/TestAction/{id?}" })] - [InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", new[] { "TESTAREA/TestController/TestAction/{id?}" })] - public void Endpoints_AreaSingleAction(string endpointInfoRoute, string[] finalEndpointTemplates) + [InlineData("{area}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{controller}/{action}/{id?}", null, new string[] { })] + [InlineData("{area=TestArea}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area=TestArea}/{controller}/{action=TestAction}/{id?}", "TestArea/TestController/{action=TestAction}/{id?}", new[] { "TestArea/TestController", "TestArea/TestController/TestAction/{id?}"})] + [InlineData("{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", "{area=TestArea}/{controller=TestController}/{action=TestAction}/{id?}", new[] { "", "TestArea", "TestArea/TestController", "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area:exists}/{controller}/{action}/{id?}", null, new[] { "TestArea/TestController/TestAction/{id?}" })] + [InlineData("{area:exists:upper-case}/{controller}/{action}/{id?}", null, new[] { "TESTAREA/TestController/TestAction/{id?}" })] + public void Endpoints_AreaSingleAction(string endpointInfoRoute, string suppressMatchingTemplate, string[] finalEndpointTemplates) { // Arrange var actionDescriptorCollection = GetActionDescriptorCollection( @@ -240,9 +262,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo(string.Empty, endpointInfoRoute, serviceProvider: services.BuildServiceProvider())); // Act - var endpoints = dataSource.Endpoints; + var endpoints = dataSource.Endpoints.ToList(); // Assert + + // Ensure there are no endpoints with duplicate Order values + Assert.DoesNotContain(endpoints.GroupBy(e => Assert.IsType(e).Order), g => g.Count() > 1); + + endpoints = endpoints.OrderBy(e => Assert.IsType(e).Order).ToList(); + + AssertSuppressMatchingTemplate(suppressMatchingTemplate, endpoints); + var inspectors = finalEndpointTemplates .Select(t => new Action(e => Assert.Equal(t, Assert.IsType(e).RoutePattern.RawText))) .ToArray(); @@ -251,6 +281,17 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Collection(endpoints, inspectors); } + private static void AssertSuppressMatchingTemplate(string suppressMatchingTemplate, List endpoints) + { + if (suppressMatchingTemplate != null) + { + var suppressMatchingEndpoint = endpoints.First(); + Assert.True(suppressMatchingEndpoint.Metadata.GetMetadata()?.SuppressMatching); + Assert.Equal(suppressMatchingTemplate, Assert.IsType(suppressMatchingEndpoint).RoutePattern.RawText); + endpoints.Remove(suppressMatchingEndpoint); + } + } + [Fact] public void Endpoints_SingleAction_ConventionalRoute_ContainsParameterWithNullRequiredRouteValue() { @@ -304,6 +345,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints, + (e) => + { + Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText); + Assert.True(e.Metadata.GetMetadata().SuppressMatching); + }, (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); } @@ -332,6 +378,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal // Assert Assert.Collection(endpoints1, + (e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText), (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); Assert.Same(endpoints1, endpoints2); @@ -373,6 +420,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; Assert.Collection(endpoints, + (e) => Assert.Equal("TestController/{action=TestAction}", Assert.IsType(e).RoutePattern.RawText), (e) => Assert.Equal("TestController", Assert.IsType(e).RoutePattern.RawText), (e) => Assert.Equal("TestController/TestAction", Assert.IsType(e).RoutePattern.RawText)); @@ -720,10 +768,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal var endpoints = dataSource.Endpoints; // Assert - var endpoint = Assert.Single(endpoints); - var matcherEndpoint = Assert.IsType(endpoint); - Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); - AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Foo/Bar", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Foo/Bar/{subscription=general}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); } [Fact] @@ -780,6 +847,464 @@ namespace Microsoft.AspNetCore.Mvc.Internal AssertIsSubset(expectedDefaults, matcherEndpoint.RoutePattern.Defaults); } + [Fact] + public void Endpoints_ConventionalRoutes_NonDefaultAndDefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "Home", action = "Index" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller}/{action=Index}/{id?}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_DefaultValuesEndingWithOptional_IncludeFullRouteAsHighPriority() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "Home", action = "Index" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller=Home}/{action=Index}/{id?}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("{controller=Home}/{action=Index}/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("Home/Index/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(4, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_DefaultValues_Shortened() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller=TestController}/{action=TestAction}/{id=17}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(1, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(2, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(3, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(4, matcherEndpoint.Order); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id=17}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(5, matcherEndpoint.Order); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_EndpointInfoDefaultsNotModified() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + var endpointInfo = CreateEndpointInfo( + name: string.Empty, + defaults: new RouteValueDictionary(), + template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}"); + dataSource.ConventionalEndpointInfos.Add(endpointInfo); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Empty(endpointInfo.Defaults); + } + + [Fact] + public void Endpoints_ConventionalRoutes_DefaultValuesAndCatchAll_Shortened() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(4, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id=17}/{**catchAll}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(5, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_DefaultValuesAndOptional_Shortened() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller=TestController}/{action=TestAction}/{id=17}/{more?}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("{controller=TestController}/{action=TestAction}/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(4, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id=17}/{more?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("17", matcherEndpoint.RoutePattern.Defaults["id"]); + Assert.Equal(5, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_OptionalExtension_IncludeFullRouteAsHighPriority() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller}/{action=TestAction}.{ext?}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/{action=TestAction}.{ext?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction.{ext?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_ConventionalRoutes_MultipleOptionalAndCatchAll_IncludeFullRouteAsHighPriority() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + dataSource.ConventionalEndpointInfos.Add(CreateEndpointInfo( + name: string.Empty, + template: "{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}")); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("{controller=TestController}/{action=TestAction}/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(3, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id?}/{more?}/{**catchAll}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(4, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_AttributeRoutes_CatchAllWithDefault_IncludeFullRouteAsHighPriority() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + "/TeamName/{*Name=DefaultName}/", + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(0, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TeamName", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TeamName/{*Name=DefaultName}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("DefaultName", matcherEndpoint.RoutePattern.Defaults["Name"]); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + }); + } + + [Fact] + public void Endpoints_AttributeRoutes_DefaultDifferentCaseFromRouteValue_UseDefaultCase() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + "{controller}/{action=TESTACTION}/{id?}", + new { controller = "TestController", action = "TestAction" }); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/{action=TESTACTION}/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]); + Assert.Equal(0, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, true); + + var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata(); + Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]); + + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]); + Assert.Equal(1, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + + var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata(); + Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]); + }, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TESTACTION/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal("TESTACTION", matcherEndpoint.RoutePattern.Defaults["action"]); + Assert.Equal(2, matcherEndpoint.Order); + AssertMatchingSuppressed(matcherEndpoint, false); + + var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata(); + Assert.Equal("TESTACTION", routeValuesAddress.RequiredValues["action"]); + }); + } + private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) @@ -898,5 +1423,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(subsetPair.Value, fullSetPairValue); } } + + private void AssertMatchingSuppressed(Endpoint endpoint, bool suppressed) + { + var isEndpointSuppressed = endpoint.Metadata.GetMetadata()?.SuppressMatching ?? false; + Assert.Equal(suppressed, isEndpointSuppressed); + } } } \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs index 6ee37de064..5cd65b9bcb 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs @@ -1041,6 +1041,136 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests Assert.Equal("Departments", result.RouteName); } + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_OptionalParameter_LinkToDefaultValuePath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Optional") + .To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("OptionalParameter", result.Action); + + Assert.Equal("/DefaultValuesRoute/Optional", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_OptionalParameter_LinkToFullPath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Optional") + .To(new { id = "123" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("OptionalParameter", result.Action); + + Assert.Equal("/DefaultValuesRoute/Optional/DEFAULTVALUES/OPTIONALPARAMETER/123", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_DefaultParameter_LinkToDefaultValuePath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Default") + .To(new { }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("DefaultParameter", result.Action); + Assert.Equal("17", result.RouteValues["id"]); + + Assert.Equal("/DefaultValuesRoute/Default", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_DefaultParameterWithCatchAll_LinkToDefaultValuePath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Default") + .To(new { catchAll = "CatchAll" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("DefaultParameter", result.Action); + Assert.Equal("17", result.RouteValues["id"]); + + Assert.Equal("/DefaultValuesRoute/Default/DEFAULTVALUES/DEFAULTPARAMETER/17/CatchAll", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_DefaultParameter_LinkToFullPath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Default") + .To(new { id = "123" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("DefaultParameter", result.Action); + Assert.Equal("17", result.RouteValues["id"]); + + Assert.Equal("/DefaultValuesRoute/Default/DEFAULTVALUES/DEFAULTPARAMETER/123", result.Link); + } + + [Fact] + public async Task ConventionalRoutedAction_DefaultValues_DefaultParameterMatches_LinkToShortenedPath() + { + // Arrange + var url = LinkFrom("http://localhost/DefaultValuesRoute/Default/DefaultValues/DefaultParameter/123") + .To(new { id = "17" }); + + // Act + var response = await Client.GetAsync(url); + + // Assert + Assert.Equal(HttpStatusCode.OK, response.StatusCode); + var body = await response.Content.ReadAsStringAsync(); + var result = JsonConvert.DeserializeObject(body); + + Assert.Equal("DefaultValues", result.Controller); + Assert.Equal("DefaultParameter", result.Action); + Assert.Equal("123", result.RouteValues["id"]); + + Assert.Equal("/DefaultValuesRoute/Default", result.Link); + } + [Fact] public virtual async Task ConventionalRoutedAction_LinkToArea() { diff --git a/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs b/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs new file mode 100644 index 0000000000..f28c5c14c5 --- /dev/null +++ b/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs @@ -0,0 +1,34 @@ +// 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.Linq; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; + +namespace RoutingWebSite +{ + public class DefaultValuesController : Controller + { + private readonly TestResponseGenerator _generator; + + public DefaultValuesController(TestResponseGenerator generator) + { + _generator = generator; + } + + public IActionResult DefaultParameter(string id) + { + return _generator.Generate(id == null + ? "/DefaultValuesRoute/DefaultValues" + : "/DefaultValuesRoute/DefaultValues/DefaultParameter/Index/" + id); + } + + public IActionResult OptionalParameter(string id) + { + return _generator.Generate(id == "17" + ? "/DefaultValuesRoute/DefaultValues" + : "/DefaultValuesRoute/DefaultValues/OptionalParameter/Index/" + id); + } + } +} \ No newline at end of file diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/test/WebSites/RoutingWebSite/Startup.cs index 03b1fa2ba2..44bc08a25f 100644 --- a/test/WebSites/RoutingWebSite/Startup.cs +++ b/test/WebSites/RoutingWebSite/Startup.cs @@ -62,6 +62,18 @@ namespace RoutingWebSite defaults: null, constraints: new { controller = "ConventionalTransformer" }); + routes.MapRoute( + "DefaultValuesRoute_OptionalParameter", + "DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}", + defaults: null, + constraints: new { controller = "DefaultValues", action = "OptionalParameter" }); + + routes.MapRoute( + "DefaultValuesRoute_DefaultParameter", + "DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}", + defaults: null, + constraints: new { controller = "DefaultValues", action = "DefaultParameter" }); + routes.MapAreaRoute( "flightRoute", "adminRoute", diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs index 473653df24..9b78b66d32 100644 --- a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs +++ b/test/WebSites/RoutingWebSite/StartupWith21Compat.cs @@ -1,14 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; -using System.Linq; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Infrastructure; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -48,7 +45,7 @@ namespace RoutingWebSite new ControllerToRemove { ControllerType = typeof(PageRouteController), - Actions = new [] { nameof(PageRouteController.AttributeRoute) } + Actions = new[] { nameof(PageRouteController.AttributeRoute) } }); services.TryAddEnumerable(ServiceDescriptor.Singleton(actionDescriptorProvider)); } @@ -64,6 +61,18 @@ namespace RoutingWebSite constraints: new { controller = "DataTokens" }, dataTokens: new { hasDataTokens = true }); + routes.MapRoute( + "DefaultValuesRoute_OptionalParameter", + "DefaultValuesRoute/Optional/{controller=DEFAULTVALUES}/{action=OPTIONALPARAMETER}/{id?}/{**catchAll}", + defaults: null, + constraints: new { controller = "DefaultValues", action = "OptionalParameter" }); + + routes.MapRoute( + "DefaultValuesRoute_DefaultParameter", + "DefaultValuesRoute/Default/{controller=DEFAULTVALUES}/{action=DEFAULTPARAMETER}/{id=17}/{**catchAll}", + defaults: null, + constraints: new { controller = "DefaultValues", action = "DefaultParameter" }); + routes.MapAreaRoute( "flightRoute", "adminRoute", From 2544926b2fc9231fa508070291eb9dc5bf2d58df Mon Sep 17 00:00:00 2001 From: Pranav K Date: Wed, 17 Oct 2018 13:58:09 -0700 Subject: [PATCH 304/316] Provide a convenience API to configure ApiBehaviorOptions --- .../MvcCoreMvcBuilderExtensions.cs | 25 +++++++++++++++++ .../MvcCoreMvcCoreBuilderExtensions.cs | 25 +++++++++++++++++ .../MvcBuilderExtensionsTest.cs | 28 +++++++++++++++++++ .../MvcCoreBuilderExtensionsTest.cs | 28 +++++++++++++++++++ 4 files changed, 106 insertions(+) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs index 6e1f9f9a1b..4830e87058 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs @@ -151,5 +151,30 @@ namespace Microsoft.Extensions.DependencyInjection builder.Services.Configure(o => o.CompatibilityVersion = version); return builder; } + + /// + /// Configures . + /// + /// The . + /// The configure action. + /// The . + public static IMvcBuilder ConfigureApiBehaviorOptions( + this IMvcBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.Services.Configure(setupAction); + + return builder; + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs index 76ebe6c261..57499c930a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs @@ -169,6 +169,31 @@ namespace Microsoft.Extensions.DependencyInjection return builder; } + /// + /// Configures . + /// + /// The . + /// The configure action. + /// The . + public static IMvcCoreBuilder ConfigureApiBehaviorOptions( + this IMvcCoreBuilder builder, + Action setupAction) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (setupAction == null) + { + throw new ArgumentNullException(nameof(setupAction)); + } + + builder.Services.Configure(setupAction); + + return builder; + } + /// /// Sets the for ASP.NET Core MVC for the application. /// diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs index 4d1dbaec0d..9ef4358c3f 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs @@ -9,6 +9,7 @@ using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.MvcServiceCollectionExtensionsTestControllers; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -110,6 +111,33 @@ namespace Microsoft.AspNetCore.Mvc Assert.Single(collection, d => d.ServiceType.Equals(typeof(ControllerTwo))); } + [Fact] + public void ConfigureApiBehaviorOptions_InvokesSetupAction() + { + // Arrange + var serviceCollection = new ServiceCollection() + .AddOptions(); + + var builder = new MvcBuilder( + serviceCollection, + new ApplicationPartManager()); + + var part = new TestApplicationPart(); + + // Act + var result = builder.ConfigureApiBehaviorOptions(o => + { + o.SuppressMapClientErrors = true; + }); + + // Assert + var options = serviceCollection. + BuildServiceProvider() + .GetRequiredService>() + .Value; + Assert.True(options.SuppressMapClientErrors); + } + private class ControllerOne { } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs index 4fc6d41572..2311cb3987 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs @@ -6,6 +6,7 @@ using System.Reflection; using Microsoft.AspNetCore.Mvc.ApplicationParts; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; using Moq; using Xunit; @@ -51,5 +52,32 @@ namespace Microsoft.AspNetCore.Mvc.DependencyInjection Assert.Same(result, builder); Assert.Equal(new ApplicationPart[] { part }, builder.PartManager.ApplicationParts.ToArray()); } + + [Fact] + public void ConfigureApiBehaviorOptions_InvokesSetupAction() + { + // Arrange + var serviceCollection = new ServiceCollection() + .AddOptions(); + + var builder = new MvcCoreBuilder( + serviceCollection, + new ApplicationPartManager()); + + var part = new TestApplicationPart(); + + // Act + var result = builder.ConfigureApiBehaviorOptions(o => + { + o.SuppressMapClientErrors = true; + }); + + // Assert + var options = serviceCollection. + BuildServiceProvider() + .GetRequiredService>() + .Value; + Assert.True(options.SuppressMapClientErrors); + } } } From 5656e7f4550bd0cc0069f0712593df0da12b1bfe Mon Sep 17 00:00:00 2001 From: Pranav K Date: Fri, 26 Oct 2018 10:48:07 -0700 Subject: [PATCH 305/316] React to CORS changes --- build/dependencies.props | 2 +- .../CorsTestsBase.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index c0129c246e..063add026b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -28,7 +28,7 @@ 2.2.0-rtm-35519 2.2.0-rtm-35519 2.2.0-rtm-35519 - 2.2.0-rtm-35519 + 2.2.0-a-rtm-fix-wildcard-16567 2.2.0-rtm-35519 2.2.0-rtm-35519 2.0.0 diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs index 3d5be3b90a..41687b9c5f 100644 --- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs +++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs @@ -196,7 +196,7 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests new[] { "true" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); Assert.Equal( - new[] { "*" }, + new[] { "header1,header2" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); Assert.Equal( new[] { "PUT,POST" }, @@ -306,8 +306,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests new[] { "true" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); Assert.Equal( - new[] { "*" }, + new[] { "Custom" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); + Assert.Equal( + new[] { "GET" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray()); var content = await response.Content.ReadAsStringAsync(); Assert.Empty(content); @@ -338,8 +341,11 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests new[] { "true" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowCredentials).ToArray()); Assert.Equal( - new[] { "*" }, + new[] { "Custom" }, responseHeaders.GetValues(CorsConstants.AccessControlAllowHeaders).ToArray()); + Assert.Equal( + new[] { "GET" }, + responseHeaders.GetValues(CorsConstants.AccessControlAllowMethods).ToArray()); var content = await response.Content.ReadAsStringAsync(); Assert.Empty(content); From 6bb292cfccbcebb2fe29d3ece848dd76d2e6ed1f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 27 Oct 2018 07:59:15 +1300 Subject: [PATCH 306/316] Add test for metadata precedence (#8642) --- .../Internal/MvcEndpointDataSource.cs | 8 ++-- .../Internal/MvcEndpointDataSourceTests.cs | 46 ++++++++++++++++--- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs index bf8df39064..df110bee25 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs @@ -577,16 +577,16 @@ namespace Microsoft.AspNetCore.Mvc.Internal bool suppressLinkGeneration, bool suppressPathMatching) { - var metadata = new List - { - action - }; + var metadata = new List(); + // Add action metadata first so it has a low precedence if (action.EndpointMetadata != null) { metadata.AddRange(action.EndpointMetadata); } + metadata.Add(action); + if (dataTokens != null) { metadata.Add(new DataTokensMetadata(dataTokens)); diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs index 618b281eba..a3e0ebe5c3 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs @@ -1305,6 +1305,36 @@ namespace Microsoft.AspNetCore.Mvc.Internal }); } + [Fact] + public void Endpoints_AttributeRoutes_ActionMetadataDoesNotOverrideDataSourceMetadata() + { + // Arrange + var actionDescriptorCollection = GetActionDescriptorCollection( + CreateActionDescriptor(new { controller = "TestController", action = "TestAction" }, + "{controller}/{action}/{id?}", + new List { new RouteValuesAddressMetadata("fakeroutename", new RouteValueDictionary(new { fake = "Fake!" })) }) + ); + var dataSource = CreateMvcEndpointDataSource(actionDescriptorCollection); + + // Act + var endpoints = dataSource.Endpoints; + + // Assert + Assert.Collection( + endpoints, + (ep) => + { + var matcherEndpoint = Assert.IsType(ep); + Assert.Equal("TestController/TestAction/{id?}", matcherEndpoint.RoutePattern.RawText); + Assert.Equal(0, matcherEndpoint.Order); + + var routeValuesAddress = matcherEndpoint.Metadata.GetMetadata(); + Assert.Equal("{controller}/{action}/{id?}", routeValuesAddress.RouteName); + Assert.Equal("TestController", routeValuesAddress.RequiredValues["controller"]); + Assert.Equal("TestAction", routeValuesAddress.RequiredValues["action"]); + }); + } + private MvcEndpointDataSource CreateMvcEndpointDataSource( IActionDescriptorCollectionProvider actionDescriptorCollectionProvider = null, MvcEndpointInvokerFactory mvcEndpointInvokerFactory = null) @@ -1381,6 +1411,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal actionDescriptors.Add(CreateActionDescriptor(requiredValue, attributeRouteTemplate)); } + return GetActionDescriptorCollection(actionDescriptors.ToArray()); + } + + private IActionDescriptorCollectionProvider GetActionDescriptorCollection(params ActionDescriptor[] actionDescriptors) + { var actionDescriptorCollectionProviderMock = new Mock(); actionDescriptorCollectionProviderMock .Setup(m => m.ActionDescriptors) @@ -1388,12 +1423,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal return actionDescriptorCollectionProviderMock.Object; } - private ActionDescriptor CreateActionDescriptor(string controller, string action, string area = null) - { - return CreateActionDescriptor(new { controller = controller, action = action, area = area }, attributeRouteTemplate: null); - } - - private ActionDescriptor CreateActionDescriptor(object requiredValues, string attributeRouteTemplate = null) + private ActionDescriptor CreateActionDescriptor( + object requiredValues, + string attributeRouteTemplate = null, + IList metadata = null) { var actionDescriptor = new ActionDescriptor(); var routeValues = new RouteValueDictionary(requiredValues); @@ -1409,6 +1442,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal Template = attributeRouteTemplate }; } + actionDescriptor.EndpointMetadata = metadata; return actionDescriptor; } From 37e562902f2a88c22c3aeaf4bc288ed266a92499 Mon Sep 17 00:00:00 2001 From: Doug Bunting Date: Thu, 11 Oct 2018 12:55:10 -0700 Subject: [PATCH 307/316] Support single `IDocumentProvider` method signature - #8593 - also find `IDocumentProvider` using a more-laborious process - `Type.GetType(string)` requires an assembly-qualified name and we don't know the assembly - default method name now `GenerateAsync` - only supported signature is `public Task GenerateAsync(string, TextWriter)` also: - handle more error cases in the tool's inside man - avoid an empty document file if `IDocumentProvider.GenerateAsync(...)` fails - unwrap an `AggregateException` nits: - remove duplicate comments - change `GetDocumentCommandWorker.TryProcess(...)` to return `false` on failure - minor because return value is currently ignored - rename `GetDocumentCommandContext.Output` -> `OutputPath` - reflect recent change to `dotnet-getdocument`'s `Resources.resx` file in its designer file --- .../Commands/GetDocumentCommand.cs | 4 +- .../Commands/GetDocumentCommandContext.cs | 2 +- .../Commands/GetDocumentCommandWorker.cs | 98 ++++++++++++++----- .../Properties/Resources.Designer.cs | 84 ++++++++++++++++ src/GetDocumentInsider/Resources.resx | 18 ++++ ...oft.Extensions.ApiDescription.Design.props | 2 +- .../Properties/Resources.Designer.cs | 4 +- 7 files changed, 182 insertions(+), 30 deletions(-) diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs index bf80df802b..11c381aac6 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommand.cs @@ -15,7 +15,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands internal class GetDocumentCommand : ProjectCommandBase { internal const string FallbackDocumentName = "v1"; - internal const string FallbackMethod = "Generate"; + internal const string FallbackMethod = "GenerateAsync"; internal const string FallbackService = "Microsoft.Extensions.ApiDescription.IDocumentProvider"; private CommandOption _documentName; @@ -139,7 +139,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands AssemblyName = Path.GetFileNameWithoutExtension(assemblyPath), DocumentName = _documentName.Value(), Method = _method.Value(), - Output = _output.Value(), + OutputPath = _output.Value(), Service = _service.Value(), }; diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs index 208139c12f..0cd0bd7f57 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs @@ -18,7 +18,7 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands public string Method { get; set; } - public string Output { get; set; } + public string OutputPath { get; set; } public string Service { get; set; } } diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs index 752d65861f..af02bd7cc3 100644 --- a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs +++ b/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs @@ -4,8 +4,8 @@ using System; using System.IO; using System.Reflection; +using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; namespace Microsoft.Extensions.ApiDescription.Tool.Commands { @@ -56,41 +56,91 @@ namespace Microsoft.Extensions.ApiDescription.Tool.Commands try { - var serviceType = Type.GetType(serviceName, throwOnError: true); - var method = serviceType.GetMethod(methodName, new[] { typeof(TextWriter), typeof(string) }); - var service = services.GetRequiredService(serviceType); - - var success = true; - using (var writer = File.CreateText(context.Output)) + Type serviceType = null; + foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) { - if (method.ReturnType == typeof(bool)) + serviceType = assembly.GetType(serviceName, throwOnError: false); + if (serviceType != null) { - success = (bool)method.Invoke(service, new object[] { writer, documentName }); - } - else - { - method.Invoke(service, new object[] { writer, documentName }); + break; } } - if (!success) + // As part of the aspnet/Mvc#8425 fix, make all warnings in this method errors unless the file already + // exists. + if (serviceType == null) { - // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists. - var message = Resources.FormatMethodInvocationFailed(methodName, serviceName, documentName); - Reporter.WriteWarning(message); + Reporter.WriteWarning(Resources.FormatServiceTypeNotFound(serviceName)); + return false; } - return success; + var method = serviceType.GetMethod(methodName, new[] { typeof(string), typeof(TextWriter) }); + if (method == null) + { + Reporter.WriteWarning(Resources.FormatMethodNotFound(methodName, serviceName)); + return false; + } + else if (!typeof(Task).IsAssignableFrom(method.ReturnType)) + { + Reporter.WriteWarning(Resources.FormatMethodReturnTypeUnsupported( + methodName, + serviceName, + method.ReturnType, + typeof(Task))); + return false; + } + + var service = services.GetService(serviceType); + if (service == null) + { + Reporter.WriteWarning(Resources.FormatServiceNotFound(serviceName)); + return false; + } + + // Create the output FileStream last to avoid corrupting an existing file or writing partial data. + var stream = new MemoryStream(); + using (var writer = new StreamWriter(stream)) + { + var resultTask = (Task)method.Invoke(service, new object[] { documentName, writer }); + if (resultTask == null) + { + Reporter.WriteWarning( + Resources.FormatMethodReturnedNull(methodName, serviceName, nameof(Task))); + return false; + } + + var finished = Task.WhenAny(resultTask, Task.Delay(TimeSpan.FromMinutes(1))); + if (!ReferenceEquals(resultTask, finished)) + { + Reporter.WriteWarning(Resources.FormatMethodTimedOut(methodName, serviceName, 1)); + return false; + } + + writer.Flush(); + stream.Position = 0L; + using (var outStream = File.Create(context.OutputPath)) + { + stream.CopyTo(outStream); + } + } + + return true; + } + catch (AggregateException ex) when (ex.InnerException != null) + { + foreach (var innerException in ex.Flatten().InnerExceptions) + { + Reporter.WriteWarning(FormatException(innerException)); + } } catch (Exception ex) { - var message = FormatException(ex); - - // As part of the aspnet/Mvc#8425 fix, make this an error unless the file already exists. - Reporter.WriteWarning(message); - - return false; + Reporter.WriteWarning(FormatException(ex)); } + + File.Delete(context.OutputPath); + + return false; } // TODO: Use Microsoft.AspNetCore.Hosting.WebHostBuilderFactory.Sources once we have dev feed available. diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/GetDocumentInsider/Properties/Resources.Designer.cs index eaec18f2fe..488d4ae93c 100644 --- a/src/GetDocumentInsider/Properties/Resources.Designer.cs +++ b/src/GetDocumentInsider/Properties/Resources.Designer.cs @@ -220,6 +220,90 @@ namespace Microsoft.Extensions.ApiDescription.Tool internal static string FormatMissingEntryPoint(object p0) => string.Format(CultureInfo.CurrentCulture, GetString("MissingEntryPoint"), p0); + /// + /// Unable to find service type '{0}' in loaded assemblies. + /// + internal static string ServiceTypeNotFound + { + get => GetString("ServiceTypeNotFound"); + } + + /// + /// Unable to find service type '{0}' in loaded assemblies. + /// + internal static string FormatServiceTypeNotFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceTypeNotFound"), p0); + + /// + /// Unable to find method named '{0}' in '{1}' implementation. + /// + internal static string MethodNotFound + { + get => GetString("MethodNotFound"); + } + + /// + /// Unable to find method named '{0}' in '{1}' implementation. + /// + internal static string FormatMethodNotFound(object p0, object p1) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodNotFound"), p0, p1); + + /// + /// Unable to find service of type '{0}' in dependency injection container. + /// + internal static string ServiceNotFound + { + get => GetString("ServiceNotFound"); + } + + /// + /// Unable to find service of type '{0}' in dependency injection container. + /// + internal static string FormatServiceNotFound(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("ServiceNotFound"), p0); + + /// + /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + /// + internal static string MethodReturnedNull + { + get => GetString("MethodReturnedNull"); + } + + /// + /// Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + /// + internal static string FormatMethodReturnedNull(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnedNull"), p0, p1, p2); + + /// + /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + /// + internal static string MethodReturnTypeUnsupported + { + get => GetString("MethodReturnTypeUnsupported"); + } + + /// + /// Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + /// + internal static string FormatMethodReturnTypeUnsupported(object p0, object p1, object p2, object p3) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodReturnTypeUnsupported"), p0, p1, p2, p3); + + /// + /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + /// + internal static string MethodTimedOut + { + get => GetString("MethodTimedOut"); + } + + /// + /// Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + /// + internal static string FormatMethodTimedOut(object p0, object p1, object p2) + => string.Format(CultureInfo.CurrentCulture, GetString("MethodTimedOut"), p0, p1, p2); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/GetDocumentInsider/Resources.resx b/src/GetDocumentInsider/Resources.resx index fffabb44f3..facc644154 100644 --- a/src/GetDocumentInsider/Resources.resx +++ b/src/GetDocumentInsider/Resources.resx @@ -162,4 +162,22 @@ Assembly '{0}' does not contain an entry point. + + Unable to find service type '{0}' in loaded assemblies. + + + Unable to find method named '{0}' in '{1}' implementation. + + + Unable to find service of type '{0}' in dependency injection container. + + + Method '{0}' of service '{1}' returned null. Must return a non-null '{2}'. + + + Method '{0}' of service '{1}' has unsupported return type '{2}'. Must return a '{3}'. + + + Method '{0}' of service '{1}' timed out. Must complete execution within {2} minute. + \ No newline at end of file diff --git a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props index 3a3177cdc9..88a40992af 100644 --- a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props +++ b/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props @@ -97,7 +97,7 @@ diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index e75fe73221..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,23 +0,0 @@ -language: csharp -sudo: required -dist: trusty -addons: - apt: - packages: - - libunwind8 -mono: none -os: -- linux -- osx -osx_image: xcode8.2 -branches: - only: - - dev - - /^release\/.*$/ - - /^(.*\/)?ci-.*$/ -before_install: -- if test "$TRAVIS_OS_NAME" == "osx"; then brew update; brew install openssl; ln -s - /usr/local/opt/openssl/lib/libcrypto.1.0.0.dylib /usr/local/lib/; ln -s /usr/local/opt/openssl/lib/libssl.1.0.0.dylib - /usr/local/lib/; fi -script: -- ./build.sh --ci diff --git a/.vsts-pipelines/builds/ci-internal.yml b/.vsts-pipelines/builds/ci-internal.yml deleted file mode 100644 index dc7b8a3cb9..0000000000 --- a/.vsts-pipelines/builds/ci-internal.yml +++ /dev/null @@ -1,13 +0,0 @@ -trigger: -- master -- release/* - -resources: - repositories: - - repository: buildtools - type: git - name: aspnet-BuildTools - ref: refs/heads/release/2.2 - -phases: -- template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/.vsts-pipelines/builds/ci-public.yml b/.vsts-pipelines/builds/ci-public.yml deleted file mode 100644 index f5087d9c30..0000000000 --- a/.vsts-pipelines/builds/ci-public.yml +++ /dev/null @@ -1,15 +0,0 @@ -trigger: -- master -- release/* - -# See https://github.com/aspnet/BuildTools -resources: - repositories: - - repository: buildtools - type: github - endpoint: DotNet-Bot GitHub Connection - name: aspnet/BuildTools - ref: refs/heads/release/2.2 - -phases: -- template: .vsts-pipelines/templates/project-ci.yml@buildtools diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index eac4268e4c..0000000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,4 +0,0 @@ -Contributing -====== - -Information on contributing to this repo is in the [Contributing Guide](https://github.com/aspnet/Home/blob/master/CONTRIBUTING.md) in the Home repo. diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index b3b180cd51..0000000000 --- a/LICENSE.txt +++ /dev/null @@ -1,201 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright (c) .NET Foundation and Contributors - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/NuGet.config b/NuGet.config deleted file mode 100644 index cec9479a72..0000000000 --- a/NuGet.config +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/build.cmd b/build.cmd deleted file mode 100644 index c0050bda12..0000000000 --- a/build.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@ECHO OFF -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' default-build %*; exit $LASTEXITCODE" diff --git a/build.sh b/build.sh deleted file mode 100755 index 98a4b22765..0000000000 --- a/build.sh +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -# Call "sync" between "chmod" and execution to prevent "text file busy" error in Docker (aufs) -chmod +x "$DIR/run.sh"; sync -"$DIR/run.sh" default-build "$@" diff --git a/korebuild-lock.txt b/korebuild-lock.txt deleted file mode 100644 index 783131ae5e..0000000000 --- a/korebuild-lock.txt +++ /dev/null @@ -1,2 +0,0 @@ -version:2.2.0-preview2-20181003.2 -commithash:41935e62d7853060283c801f49992e2c73a95927 diff --git a/korebuild.json b/korebuild.json deleted file mode 100644 index d217d06e3e..0000000000 --- a/korebuild.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/release/2.2/tools/korebuild.schema.json", - "channel": "release/2.2" -} diff --git a/run.cmd b/run.cmd deleted file mode 100644 index d52d5c7e68..0000000000 --- a/run.cmd +++ /dev/null @@ -1,2 +0,0 @@ -@ECHO OFF -PowerShell -NoProfile -NoLogo -ExecutionPolicy unrestricted -Command "[System.Threading.Thread]::CurrentThread.CurrentCulture = ''; [System.Threading.Thread]::CurrentThread.CurrentUICulture = '';& '%~dp0run.ps1' %*; exit $LASTEXITCODE" diff --git a/run.ps1 b/run.ps1 deleted file mode 100644 index 34604c7175..0000000000 --- a/run.ps1 +++ /dev/null @@ -1,209 +0,0 @@ -#!/usr/bin/env powershell -#requires -version 4 - -<# -.SYNOPSIS -Executes KoreBuild commands. - -.DESCRIPTION -Downloads korebuild if required. Then executes the KoreBuild command. To see available commands, execute with `-Command help`. - -.PARAMETER Command -The KoreBuild command to run. - -.PARAMETER Path -The folder to build. Defaults to the folder containing this script. - -.PARAMETER Channel -The channel of KoreBuild to download. Overrides the value from the config file. - -.PARAMETER DotNetHome -The directory where .NET Core tools will be stored. - -.PARAMETER ToolsSource -The base url where build tools can be downloaded. Overrides the value from the config file. - -.PARAMETER Update -Updates KoreBuild to the latest version even if a lock file is present. - -.PARAMETER Reinstall -Re-installs KoreBuild - -.PARAMETER ConfigFile -The path to the configuration file that stores values. Defaults to korebuild.json. - -.PARAMETER ToolsSourceSuffix -The Suffix to append to the end of the ToolsSource. Useful for query strings in blob stores. - -.PARAMETER CI -Sets up CI specific settings and variables. - -.PARAMETER Arguments -Arguments to be passed to the command - -.NOTES -This function will create a file $PSScriptRoot/korebuild-lock.txt. This lock file can be committed to source, but does not have to be. -When the lockfile is not present, KoreBuild will create one using latest available version from $Channel. - -The $ConfigFile is expected to be an JSON file. It is optional, and the configuration values in it are optional as well. Any options set -in the file are overridden by command line parameters. - -.EXAMPLE -Example config file: -```json -{ - "$schema": "https://raw.githubusercontent.com/aspnet/BuildTools/master/tools/korebuild.schema.json", - "channel": "master", - "toolsSource": "https://aspnetcore.blob.core.windows.net/buildtools" -} -``` -#> -[CmdletBinding(PositionalBinding = $false)] -param( - [Parameter(Mandatory = $true, Position = 0)] - [string]$Command, - [string]$Path = $PSScriptRoot, - [Alias('c')] - [string]$Channel, - [Alias('d')] - [string]$DotNetHome, - [Alias('s')] - [string]$ToolsSource, - [Alias('u')] - [switch]$Update, - [switch]$Reinstall, - [string]$ToolsSourceSuffix, - [string]$ConfigFile = $null, - [switch]$CI, - [Parameter(ValueFromRemainingArguments = $true)] - [string[]]$Arguments -) - -Set-StrictMode -Version 2 -$ErrorActionPreference = 'Stop' - -# -# Functions -# - -function Get-KoreBuild { - - $lockFile = Join-Path $Path 'korebuild-lock.txt' - - if (!(Test-Path $lockFile) -or $Update) { - Get-RemoteFile "$ToolsSource/korebuild/channels/$Channel/latest.txt" $lockFile $ToolsSourceSuffix - } - - $version = Get-Content $lockFile | Where-Object { $_ -like 'version:*' } | Select-Object -first 1 - if (!$version) { - Write-Error "Failed to parse version from $lockFile. Expected a line that begins with 'version:'" - } - $version = $version.TrimStart('version:').Trim() - $korebuildPath = Join-Paths $DotNetHome ('buildtools', 'korebuild', $version) - - if ($Reinstall -and (Test-Path $korebuildPath)) { - Remove-Item -Force -Recurse $korebuildPath - } - - if (!(Test-Path $korebuildPath)) { - Write-Host -ForegroundColor Magenta "Downloading KoreBuild $version" - New-Item -ItemType Directory -Path $korebuildPath | Out-Null - $remotePath = "$ToolsSource/korebuild/artifacts/$version/korebuild.$version.zip" - - try { - $tmpfile = Join-Path ([IO.Path]::GetTempPath()) "KoreBuild-$([guid]::NewGuid()).zip" - Get-RemoteFile $remotePath $tmpfile $ToolsSourceSuffix - if (Get-Command -Name 'Microsoft.PowerShell.Archive\Expand-Archive' -ErrorAction Ignore) { - # Use built-in commands where possible as they are cross-plat compatible - Microsoft.PowerShell.Archive\Expand-Archive -Path $tmpfile -DestinationPath $korebuildPath - } - else { - # Fallback to old approach for old installations of PowerShell - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::ExtractToDirectory($tmpfile, $korebuildPath) - } - } - catch { - Remove-Item -Recurse -Force $korebuildPath -ErrorAction Ignore - throw - } - finally { - Remove-Item $tmpfile -ErrorAction Ignore - } - } - - return $korebuildPath -} - -function Join-Paths([string]$path, [string[]]$childPaths) { - $childPaths | ForEach-Object { $path = Join-Path $path $_ } - return $path -} - -function Get-RemoteFile([string]$RemotePath, [string]$LocalPath, [string]$RemoteSuffix) { - if ($RemotePath -notlike 'http*') { - Copy-Item $RemotePath $LocalPath - return - } - - $retries = 10 - while ($retries -gt 0) { - $retries -= 1 - try { - Invoke-WebRequest -UseBasicParsing -Uri $($RemotePath + $RemoteSuffix) -OutFile $LocalPath - return - } - catch { - Write-Verbose "Request failed. $retries retries remaining" - } - } - - Write-Error "Download failed: '$RemotePath'." -} - -# -# Main -# - -# Load configuration or set defaults - -$Path = Resolve-Path $Path -if (!$ConfigFile) { $ConfigFile = Join-Path $Path 'korebuild.json' } - -if (Test-Path $ConfigFile) { - try { - $config = Get-Content -Raw -Encoding UTF8 -Path $ConfigFile | ConvertFrom-Json - if ($config) { - if (!($Channel) -and (Get-Member -Name 'channel' -InputObject $config)) { [string] $Channel = $config.channel } - if (!($ToolsSource) -and (Get-Member -Name 'toolsSource' -InputObject $config)) { [string] $ToolsSource = $config.toolsSource} - } - } - catch { - Write-Host -ForegroundColor Red $Error[0] - Write-Error "$ConfigFile contains invalid JSON." - exit 1 - } -} - -if (!$DotNetHome) { - $DotNetHome = if ($env:DOTNET_HOME) { $env:DOTNET_HOME } ` - elseif ($env:USERPROFILE) { Join-Path $env:USERPROFILE '.dotnet'} ` - elseif ($env:HOME) {Join-Path $env:HOME '.dotnet'}` - else { Join-Path $PSScriptRoot '.dotnet'} -} - -if (!$Channel) { $Channel = 'master' } -if (!$ToolsSource) { $ToolsSource = 'https://aspnetcore.blob.core.windows.net/buildtools' } - -# Execute - -$korebuildPath = Get-KoreBuild -Import-Module -Force -Scope Local (Join-Path $korebuildPath 'KoreBuild.psd1') - -try { - Set-KoreBuildSettings -ToolsSource $ToolsSource -DotNetHome $DotNetHome -RepoPath $Path -ConfigFile $ConfigFile -CI:$CI - Invoke-KoreBuildCommand $Command @Arguments -} -finally { - Remove-Module 'KoreBuild' -ErrorAction Ignore -} diff --git a/run.sh b/run.sh deleted file mode 100755 index 4c1fed5646..0000000000 --- a/run.sh +++ /dev/null @@ -1,256 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -# -# variables -# - -RESET="\033[0m" -RED="\033[0;31m" -YELLOW="\033[0;33m" -MAGENTA="\033[0;95m" -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -[ -z "${DOTNET_HOME:-}" ] && DOTNET_HOME="$HOME/.dotnet" -verbose=false -update=false -reinstall=false -repo_path="$DIR" -channel='' -tools_source='' -tools_source_suffix='' -ci=false - -# -# Functions -# -__usage() { - echo "Usage: $(basename "${BASH_SOURCE[0]}") command [options] [[--] ...]" - echo "" - echo "Arguments:" - echo " command The command to be run." - echo " ... Arguments passed to the command. Variable number of arguments allowed." - echo "" - echo "Options:" - echo " --verbose Show verbose output." - echo " -c|--channel The channel of KoreBuild to download. Overrides the value from the config file.." - echo " --config-file The path to the configuration file that stores values. Defaults to korebuild.json." - echo " -d|--dotnet-home The directory where .NET Core tools will be stored. Defaults to '\$DOTNET_HOME' or '\$HOME/.dotnet." - echo " --path The directory to build. Defaults to the directory containing the script." - echo " -s|--tools-source|-ToolsSource The base url where build tools can be downloaded. Overrides the value from the config file." - echo " --tools-source-suffix|-ToolsSourceSuffix The suffix to append to tools-source. Useful for query strings." - echo " -u|--update Update to the latest KoreBuild even if the lock file is present." - echo " --reinstall Reinstall KoreBuild." - echo " --ci Apply CI specific settings and environment variables." - echo "" - echo "Description:" - echo " This function will create a file \$DIR/korebuild-lock.txt. This lock file can be committed to source, but does not have to be." - echo " When the lockfile is not present, KoreBuild will create one using latest available version from \$channel." - - if [[ "${1:-}" != '--no-exit' ]]; then - exit 2 - fi -} - -get_korebuild() { - local version - local lock_file="$repo_path/korebuild-lock.txt" - if [ ! -f "$lock_file" ] || [ "$update" = true ]; then - __get_remote_file "$tools_source/korebuild/channels/$channel/latest.txt" "$lock_file" "$tools_source_suffix" - fi - version="$(grep 'version:*' -m 1 "$lock_file")" - if [[ "$version" == '' ]]; then - __error "Failed to parse version from $lock_file. Expected a line that begins with 'version:'" - return 1 - fi - version="$(echo "${version#version:}" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')" - local korebuild_path="$DOTNET_HOME/buildtools/korebuild/$version" - - if [ "$reinstall" = true ] && [ -d "$korebuild_path" ]; then - rm -rf "$korebuild_path" - fi - - { - if [ ! -d "$korebuild_path" ]; then - mkdir -p "$korebuild_path" - local remote_path="$tools_source/korebuild/artifacts/$version/korebuild.$version.zip" - tmpfile="$(mktemp)" - echo -e "${MAGENTA}Downloading KoreBuild ${version}${RESET}" - if __get_remote_file "$remote_path" "$tmpfile" "$tools_source_suffix"; then - unzip -q -d "$korebuild_path" "$tmpfile" - fi - rm "$tmpfile" || true - fi - - source "$korebuild_path/KoreBuild.sh" - } || { - if [ -d "$korebuild_path" ]; then - echo "Cleaning up after failed installation" - rm -rf "$korebuild_path" || true - fi - return 1 - } -} - -__error() { - echo -e "${RED}error: $*${RESET}" 1>&2 -} - -__warn() { - echo -e "${YELLOW}warning: $*${RESET}" -} - -__machine_has() { - hash "$1" > /dev/null 2>&1 - return $? -} - -__get_remote_file() { - local remote_path=$1 - local local_path=$2 - local remote_path_suffix=$3 - - if [[ "$remote_path" != 'http'* ]]; then - cp "$remote_path" "$local_path" - return 0 - fi - - local failed=false - if __machine_has wget; then - wget --tries 10 --quiet -O "$local_path" "${remote_path}${remote_path_suffix}" || failed=true - else - failed=true - fi - - if [ "$failed" = true ] && __machine_has curl; then - failed=false - curl --retry 10 -sSL -f --create-dirs -o "$local_path" "${remote_path}${remote_path_suffix}" || failed=true - fi - - if [ "$failed" = true ]; then - __error "Download failed: $remote_path" 1>&2 - return 1 - fi -} - -# -# main -# - -command="${1:-}" -shift - -while [[ $# -gt 0 ]]; do - case $1 in - -\?|-h|--help) - __usage --no-exit - exit 0 - ;; - -c|--channel|-Channel) - shift - channel="${1:-}" - [ -z "$channel" ] && __usage - ;; - --config-file|-ConfigFile) - shift - config_file="${1:-}" - [ -z "$config_file" ] && __usage - if [ ! -f "$config_file" ]; then - __error "Invalid value for --config-file. $config_file does not exist." - exit 1 - fi - ;; - -d|--dotnet-home|-DotNetHome) - shift - DOTNET_HOME="${1:-}" - [ -z "$DOTNET_HOME" ] && __usage - ;; - --path|-Path) - shift - repo_path="${1:-}" - [ -z "$repo_path" ] && __usage - ;; - -s|--tools-source|-ToolsSource) - shift - tools_source="${1:-}" - [ -z "$tools_source" ] && __usage - ;; - --tools-source-suffix|-ToolsSourceSuffix) - shift - tools_source_suffix="${1:-}" - [ -z "$tools_source_suffix" ] && __usage - ;; - -u|--update|-Update) - update=true - ;; - --reinstall|-[Rr]einstall) - reinstall=true - ;; - --ci|-[Cc][Ii]) - ci=true - ;; - --verbose|-Verbose) - verbose=true - ;; - --) - shift - break - ;; - *) - break - ;; - esac - shift -done - -if ! __machine_has unzip; then - __error 'Missing required command: unzip' - exit 1 -fi - -if ! __machine_has curl && ! __machine_has wget; then - __error 'Missing required command. Either wget or curl is required.' - exit 1 -fi - -[ -z "${config_file:-}" ] && config_file="$repo_path/korebuild.json" -if [ -f "$config_file" ]; then - if __machine_has jq ; then - if jq '.' "$config_file" >/dev/null ; then - config_channel="$(jq -r 'select(.channel!=null) | .channel' "$config_file")" - config_tools_source="$(jq -r 'select(.toolsSource!=null) | .toolsSource' "$config_file")" - else - __error "$config_file contains invalid JSON." - exit 1 - fi - elif __machine_has python ; then - if python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then - config_channel="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" - config_tools_source="$(python -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" - else - __error "$config_file contains invalid JSON." - exit 1 - fi - elif __machine_has python3 ; then - if python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'))" >/dev/null ; then - config_channel="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['channel'] if 'channel' in obj else '')")" - config_tools_source="$(python3 -c "import json,codecs;obj=json.load(codecs.open('$config_file', 'r', 'utf-8-sig'));print(obj['toolsSource'] if 'toolsSource' in obj else '')")" - else - __error "$config_file contains invalid JSON." - exit 1 - fi - else - __error 'Missing required command: jq or python. Could not parse the JSON file.' - exit 1 - fi - - [ ! -z "${config_channel:-}" ] && channel="$config_channel" - [ ! -z "${config_tools_source:-}" ] && tools_source="$config_tools_source" -fi - -[ -z "$channel" ] && channel='master' -[ -z "$tools_source" ] && tools_source='https://aspnetcore.blob.core.windows.net/buildtools' - -get_korebuild -set_korebuildsettings "$tools_source" "$DOTNET_HOME" "$repo_path" "$config_file" "$ci" -invoke_korebuild_command "$command" "$@" diff --git a/.editorconfig b/src/Mvc/.editorconfig similarity index 100% rename from .editorconfig rename to src/Mvc/.editorconfig diff --git a/.gitignore b/src/Mvc/.gitignore similarity index 100% rename from .gitignore rename to src/Mvc/.gitignore diff --git a/Directory.Build.props b/src/Mvc/Directory.Build.props similarity index 100% rename from Directory.Build.props rename to src/Mvc/Directory.Build.props diff --git a/Directory.Build.targets b/src/Mvc/Directory.Build.targets similarity index 100% rename from Directory.Build.targets rename to src/Mvc/Directory.Build.targets diff --git a/Mvc.NoFun.sln b/src/Mvc/Mvc.NoFun.sln similarity index 100% rename from Mvc.NoFun.sln rename to src/Mvc/Mvc.NoFun.sln diff --git a/Mvc.sln b/src/Mvc/Mvc.sln similarity index 100% rename from Mvc.sln rename to src/Mvc/Mvc.sln diff --git a/NuGetPackageVerifier.json b/src/Mvc/NuGetPackageVerifier.json similarity index 100% rename from NuGetPackageVerifier.json rename to src/Mvc/NuGetPackageVerifier.json diff --git a/README.md b/src/Mvc/README.md similarity index 100% rename from README.md rename to src/Mvc/README.md diff --git a/Settings.StyleCop b/src/Mvc/Settings.StyleCop similarity index 100% rename from Settings.StyleCop rename to src/Mvc/Settings.StyleCop diff --git a/benchmarkapps/BasicApi/BasicApi.csproj b/src/Mvc/benchmarkapps/BasicApi/BasicApi.csproj similarity index 100% rename from benchmarkapps/BasicApi/BasicApi.csproj rename to src/Mvc/benchmarkapps/BasicApi/BasicApi.csproj diff --git a/benchmarkapps/BasicApi/Controllers/PetController.cs b/src/Mvc/benchmarkapps/BasicApi/Controllers/PetController.cs similarity index 100% rename from benchmarkapps/BasicApi/Controllers/PetController.cs rename to src/Mvc/benchmarkapps/BasicApi/Controllers/PetController.cs diff --git a/benchmarkapps/BasicApi/Controllers/TokenController.cs b/src/Mvc/benchmarkapps/BasicApi/Controllers/TokenController.cs similarity index 100% rename from benchmarkapps/BasicApi/Controllers/TokenController.cs rename to src/Mvc/benchmarkapps/BasicApi/Controllers/TokenController.cs diff --git a/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.Designer.cs b/src/Mvc/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.Designer.cs similarity index 100% rename from benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.Designer.cs rename to src/Mvc/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.Designer.cs diff --git a/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.cs b/src/Mvc/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.cs similarity index 100% rename from benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.cs rename to src/Mvc/benchmarkapps/BasicApi/Migrations/20180609000420_InitialCreate.cs diff --git a/benchmarkapps/BasicApi/Migrations/BasicApiContextModelSnapshot.cs b/src/Mvc/benchmarkapps/BasicApi/Migrations/BasicApiContextModelSnapshot.cs similarity index 100% rename from benchmarkapps/BasicApi/Migrations/BasicApiContextModelSnapshot.cs rename to src/Mvc/benchmarkapps/BasicApi/Migrations/BasicApiContextModelSnapshot.cs diff --git a/benchmarkapps/BasicApi/Models/BasicApiContext.cs b/src/Mvc/benchmarkapps/BasicApi/Models/BasicApiContext.cs similarity index 100% rename from benchmarkapps/BasicApi/Models/BasicApiContext.cs rename to src/Mvc/benchmarkapps/BasicApi/Models/BasicApiContext.cs diff --git a/benchmarkapps/BasicApi/Models/Category.cs b/src/Mvc/benchmarkapps/BasicApi/Models/Category.cs similarity index 100% rename from benchmarkapps/BasicApi/Models/Category.cs rename to src/Mvc/benchmarkapps/BasicApi/Models/Category.cs diff --git a/benchmarkapps/BasicApi/Models/Image.cs b/src/Mvc/benchmarkapps/BasicApi/Models/Image.cs similarity index 100% rename from benchmarkapps/BasicApi/Models/Image.cs rename to src/Mvc/benchmarkapps/BasicApi/Models/Image.cs diff --git a/benchmarkapps/BasicApi/Models/Pet.cs b/src/Mvc/benchmarkapps/BasicApi/Models/Pet.cs similarity index 100% rename from benchmarkapps/BasicApi/Models/Pet.cs rename to src/Mvc/benchmarkapps/BasicApi/Models/Pet.cs diff --git a/benchmarkapps/BasicApi/Models/Tag.cs b/src/Mvc/benchmarkapps/BasicApi/Models/Tag.cs similarity index 100% rename from benchmarkapps/BasicApi/Models/Tag.cs rename to src/Mvc/benchmarkapps/BasicApi/Models/Tag.cs diff --git a/benchmarkapps/BasicApi/Startup.cs b/src/Mvc/benchmarkapps/BasicApi/Startup.cs similarity index 100% rename from benchmarkapps/BasicApi/Startup.cs rename to src/Mvc/benchmarkapps/BasicApi/Startup.cs diff --git a/benchmarkapps/BasicApi/benchmarks.json b/src/Mvc/benchmarkapps/BasicApi/benchmarks.json similarity index 100% rename from benchmarkapps/BasicApi/benchmarks.json rename to src/Mvc/benchmarkapps/BasicApi/benchmarks.json diff --git a/benchmarkapps/BasicApi/getWithToken.lua b/src/Mvc/benchmarkapps/BasicApi/getWithToken.lua similarity index 100% rename from benchmarkapps/BasicApi/getWithToken.lua rename to src/Mvc/benchmarkapps/BasicApi/getWithToken.lua diff --git a/benchmarkapps/BasicApi/postJsonWithToken.lua b/src/Mvc/benchmarkapps/BasicApi/postJsonWithToken.lua similarity index 100% rename from benchmarkapps/BasicApi/postJsonWithToken.lua rename to src/Mvc/benchmarkapps/BasicApi/postJsonWithToken.lua diff --git a/benchmarkapps/BasicApi/runtimeconfig.template.json b/src/Mvc/benchmarkapps/BasicApi/runtimeconfig.template.json similarity index 100% rename from benchmarkapps/BasicApi/runtimeconfig.template.json rename to src/Mvc/benchmarkapps/BasicApi/runtimeconfig.template.json diff --git a/benchmarkapps/BasicViews/BasicViews.csproj b/src/Mvc/benchmarkapps/BasicViews/BasicViews.csproj similarity index 100% rename from benchmarkapps/BasicViews/BasicViews.csproj rename to src/Mvc/benchmarkapps/BasicViews/BasicViews.csproj diff --git a/benchmarkapps/BasicViews/BasicViewsContext.cs b/src/Mvc/benchmarkapps/BasicViews/BasicViewsContext.cs similarity index 100% rename from benchmarkapps/BasicViews/BasicViewsContext.cs rename to src/Mvc/benchmarkapps/BasicViews/BasicViewsContext.cs diff --git a/benchmarkapps/BasicViews/Components/CurrentUser.cs b/src/Mvc/benchmarkapps/BasicViews/Components/CurrentUser.cs similarity index 100% rename from benchmarkapps/BasicViews/Components/CurrentUser.cs rename to src/Mvc/benchmarkapps/BasicViews/Components/CurrentUser.cs diff --git a/benchmarkapps/BasicViews/Controllers/HomeController.cs b/src/Mvc/benchmarkapps/BasicViews/Controllers/HomeController.cs similarity index 100% rename from benchmarkapps/BasicViews/Controllers/HomeController.cs rename to src/Mvc/benchmarkapps/BasicViews/Controllers/HomeController.cs diff --git a/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.Designer.cs b/src/Mvc/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.Designer.cs similarity index 100% rename from benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.Designer.cs rename to src/Mvc/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.Designer.cs diff --git a/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.cs b/src/Mvc/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.cs similarity index 100% rename from benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.cs rename to src/Mvc/benchmarkapps/BasicViews/Migrations/20180609000611_InitialCreate.cs diff --git a/benchmarkapps/BasicViews/Migrations/BasicViewsContextModelSnapshot.cs b/src/Mvc/benchmarkapps/BasicViews/Migrations/BasicViewsContextModelSnapshot.cs similarity index 100% rename from benchmarkapps/BasicViews/Migrations/BasicViewsContextModelSnapshot.cs rename to src/Mvc/benchmarkapps/BasicViews/Migrations/BasicViewsContextModelSnapshot.cs diff --git a/benchmarkapps/BasicViews/Person.cs b/src/Mvc/benchmarkapps/BasicViews/Person.cs similarity index 100% rename from benchmarkapps/BasicViews/Person.cs rename to src/Mvc/benchmarkapps/BasicViews/Person.cs diff --git a/benchmarkapps/BasicViews/Startup.cs b/src/Mvc/benchmarkapps/BasicViews/Startup.cs similarity index 100% rename from benchmarkapps/BasicViews/Startup.cs rename to src/Mvc/benchmarkapps/BasicViews/Startup.cs diff --git a/benchmarkapps/BasicViews/Views/Home/HtmlHelpers.cshtml b/src/Mvc/benchmarkapps/BasicViews/Views/Home/HtmlHelpers.cshtml similarity index 100% rename from benchmarkapps/BasicViews/Views/Home/HtmlHelpers.cshtml rename to src/Mvc/benchmarkapps/BasicViews/Views/Home/HtmlHelpers.cshtml diff --git a/benchmarkapps/BasicViews/Views/Home/Index.cshtml b/src/Mvc/benchmarkapps/BasicViews/Views/Home/Index.cshtml similarity index 100% rename from benchmarkapps/BasicViews/Views/Home/Index.cshtml rename to src/Mvc/benchmarkapps/BasicViews/Views/Home/Index.cshtml diff --git a/benchmarkapps/BasicViews/Views/Shared/_Layout.cshtml b/src/Mvc/benchmarkapps/BasicViews/Views/Shared/_Layout.cshtml similarity index 100% rename from benchmarkapps/BasicViews/Views/Shared/_Layout.cshtml rename to src/Mvc/benchmarkapps/BasicViews/Views/Shared/_Layout.cshtml diff --git a/benchmarkapps/BasicViews/Views/_ViewImports.cshtml b/src/Mvc/benchmarkapps/BasicViews/Views/_ViewImports.cshtml similarity index 100% rename from benchmarkapps/BasicViews/Views/_ViewImports.cshtml rename to src/Mvc/benchmarkapps/BasicViews/Views/_ViewImports.cshtml diff --git a/benchmarkapps/BasicViews/Views/_ViewStart.cshtml b/src/Mvc/benchmarkapps/BasicViews/Views/_ViewStart.cshtml similarity index 100% rename from benchmarkapps/BasicViews/Views/_ViewStart.cshtml rename to src/Mvc/benchmarkapps/BasicViews/Views/_ViewStart.cshtml diff --git a/benchmarkapps/BasicViews/benchmarks.json b/src/Mvc/benchmarkapps/BasicViews/benchmarks.json similarity index 100% rename from benchmarkapps/BasicViews/benchmarks.json rename to src/Mvc/benchmarkapps/BasicViews/benchmarks.json diff --git a/benchmarkapps/BasicViews/post.lua b/src/Mvc/benchmarkapps/BasicViews/post.lua similarity index 100% rename from benchmarkapps/BasicViews/post.lua rename to src/Mvc/benchmarkapps/BasicViews/post.lua diff --git a/benchmarkapps/BasicViews/postWithToken.lua b/src/Mvc/benchmarkapps/BasicViews/postWithToken.lua similarity index 100% rename from benchmarkapps/BasicViews/postWithToken.lua rename to src/Mvc/benchmarkapps/BasicViews/postWithToken.lua diff --git a/benchmarkapps/BasicViews/runtimeconfig.template.json b/src/Mvc/benchmarkapps/BasicViews/runtimeconfig.template.json similarity index 100% rename from benchmarkapps/BasicViews/runtimeconfig.template.json rename to src/Mvc/benchmarkapps/BasicViews/runtimeconfig.template.json diff --git a/benchmarkapps/BasicViews/web.config b/src/Mvc/benchmarkapps/BasicViews/web.config similarity index 100% rename from benchmarkapps/BasicViews/web.config rename to src/Mvc/benchmarkapps/BasicViews/web.config diff --git a/benchmarkapps/BasicViews/wwwroot/css/site.css b/src/Mvc/benchmarkapps/BasicViews/wwwroot/css/site.css similarity index 100% rename from benchmarkapps/BasicViews/wwwroot/css/site.css rename to src/Mvc/benchmarkapps/BasicViews/wwwroot/css/site.css diff --git a/benchmarkapps/BasicViews/wwwroot/css/site.min.css b/src/Mvc/benchmarkapps/BasicViews/wwwroot/css/site.min.css similarity index 100% rename from benchmarkapps/BasicViews/wwwroot/css/site.min.css rename to src/Mvc/benchmarkapps/BasicViews/wwwroot/css/site.min.css diff --git a/benchmarkapps/BasicViews/wwwroot/js/site.js b/src/Mvc/benchmarkapps/BasicViews/wwwroot/js/site.js similarity index 100% rename from benchmarkapps/BasicViews/wwwroot/js/site.js rename to src/Mvc/benchmarkapps/BasicViews/wwwroot/js/site.js diff --git a/benchmarkapps/BasicViews/wwwroot/js/site.min.js b/src/Mvc/benchmarkapps/BasicViews/wwwroot/js/site.min.js similarity index 100% rename from benchmarkapps/BasicViews/wwwroot/js/site.min.js rename to src/Mvc/benchmarkapps/BasicViews/wwwroot/js/site.min.js diff --git a/benchmarkapps/README.md b/src/Mvc/benchmarkapps/README.md similarity index 100% rename from benchmarkapps/README.md rename to src/Mvc/benchmarkapps/README.md diff --git a/benchmarkapps/RazorRendering/Data/DataA.cs b/src/Mvc/benchmarkapps/RazorRendering/Data/DataA.cs similarity index 100% rename from benchmarkapps/RazorRendering/Data/DataA.cs rename to src/Mvc/benchmarkapps/RazorRendering/Data/DataA.cs diff --git a/benchmarkapps/RazorRendering/Data/DataB.cs b/src/Mvc/benchmarkapps/RazorRendering/Data/DataB.cs similarity index 100% rename from benchmarkapps/RazorRendering/Data/DataB.cs rename to src/Mvc/benchmarkapps/RazorRendering/Data/DataB.cs diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml b/src/Mvc/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml similarity index 100% rename from benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml rename to src/Mvc/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml diff --git a/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs b/src/Mvc/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs similarity index 100% rename from benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs rename to src/Mvc/benchmarkapps/RazorRendering/Pages/Category/PageA.cshtml.cs diff --git a/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml b/src/Mvc/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml similarity index 100% rename from benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml rename to src/Mvc/benchmarkapps/RazorRendering/Pages/Category/_Subcategories.cshtml diff --git a/benchmarkapps/RazorRendering/Pages/Page.cs b/src/Mvc/benchmarkapps/RazorRendering/Pages/Page.cs similarity index 100% rename from benchmarkapps/RazorRendering/Pages/Page.cs rename to src/Mvc/benchmarkapps/RazorRendering/Pages/Page.cs diff --git a/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml b/src/Mvc/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml similarity index 100% rename from benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml rename to src/Mvc/benchmarkapps/RazorRendering/Pages/Shared/_Layout.cshtml diff --git a/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml b/src/Mvc/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml similarity index 100% rename from benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml rename to src/Mvc/benchmarkapps/RazorRendering/Pages/_ViewImports.cshtml diff --git a/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml b/src/Mvc/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml similarity index 100% rename from benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml rename to src/Mvc/benchmarkapps/RazorRendering/Pages/_ViewStart.cshtml diff --git a/benchmarkapps/RazorRendering/RazorRendering.csproj b/src/Mvc/benchmarkapps/RazorRendering/RazorRendering.csproj similarity index 100% rename from benchmarkapps/RazorRendering/RazorRendering.csproj rename to src/Mvc/benchmarkapps/RazorRendering/RazorRendering.csproj diff --git a/benchmarkapps/RazorRendering/Readme.md b/src/Mvc/benchmarkapps/RazorRendering/Readme.md similarity index 100% rename from benchmarkapps/RazorRendering/Readme.md rename to src/Mvc/benchmarkapps/RazorRendering/Readme.md diff --git a/benchmarkapps/RazorRendering/Startup.cs b/src/Mvc/benchmarkapps/RazorRendering/Startup.cs similarity index 100% rename from benchmarkapps/RazorRendering/Startup.cs rename to src/Mvc/benchmarkapps/RazorRendering/Startup.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ActionSelectorBenchmark.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/AssemblyInfo.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/Microsoft.AspNetCore.Mvc.Performance.csproj diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/MvcEndpointDatasourceBenchmark.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorBenchmarkBase.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorByteArrayBenchmark.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/ValidationVisitorModelWithValidatedProperties.cs diff --git a/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md b/src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md similarity index 100% rename from benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md rename to src/Mvc/benchmarks/Microsoft.AspNetCore.Mvc.Performance/readme.md diff --git a/build/Key.snk b/src/Mvc/build/Key.snk similarity index 100% rename from build/Key.snk rename to src/Mvc/build/Key.snk diff --git a/build/buildpipeline/linux.groovy b/src/Mvc/build/buildpipeline/linux.groovy similarity index 100% rename from build/buildpipeline/linux.groovy rename to src/Mvc/build/buildpipeline/linux.groovy diff --git a/build/buildpipeline/osx.groovy b/src/Mvc/build/buildpipeline/osx.groovy similarity index 100% rename from build/buildpipeline/osx.groovy rename to src/Mvc/build/buildpipeline/osx.groovy diff --git a/build/buildpipeline/pipeline.groovy b/src/Mvc/build/buildpipeline/pipeline.groovy similarity index 100% rename from build/buildpipeline/pipeline.groovy rename to src/Mvc/build/buildpipeline/pipeline.groovy diff --git a/build/buildpipeline/windows.groovy b/src/Mvc/build/buildpipeline/windows.groovy similarity index 100% rename from build/buildpipeline/windows.groovy rename to src/Mvc/build/buildpipeline/windows.groovy diff --git a/build/dependencies.props b/src/Mvc/build/dependencies.props similarity index 100% rename from build/dependencies.props rename to src/Mvc/build/dependencies.props diff --git a/build/repo.props b/src/Mvc/build/repo.props similarity index 100% rename from build/repo.props rename to src/Mvc/build/repo.props diff --git a/build/sources.props b/src/Mvc/build/sources.props similarity index 100% rename from build/sources.props rename to src/Mvc/build/sources.props diff --git a/global.json b/src/Mvc/global.json similarity index 100% rename from global.json rename to src/Mvc/global.json diff --git a/samples/MvcSandbox/.bowerrc b/src/Mvc/samples/MvcSandbox/.bowerrc similarity index 100% rename from samples/MvcSandbox/.bowerrc rename to src/Mvc/samples/MvcSandbox/.bowerrc diff --git a/samples/MvcSandbox/.gitignore b/src/Mvc/samples/MvcSandbox/.gitignore similarity index 100% rename from samples/MvcSandbox/.gitignore rename to src/Mvc/samples/MvcSandbox/.gitignore diff --git a/samples/MvcSandbox/Controllers/HomeController.cs b/src/Mvc/samples/MvcSandbox/Controllers/HomeController.cs similarity index 100% rename from samples/MvcSandbox/Controllers/HomeController.cs rename to src/Mvc/samples/MvcSandbox/Controllers/HomeController.cs diff --git a/samples/MvcSandbox/Models/Index.cs b/src/Mvc/samples/MvcSandbox/Models/Index.cs similarity index 100% rename from samples/MvcSandbox/Models/Index.cs rename to src/Mvc/samples/MvcSandbox/Models/Index.cs diff --git a/samples/MvcSandbox/Models/TestModel.cs b/src/Mvc/samples/MvcSandbox/Models/TestModel.cs similarity index 100% rename from samples/MvcSandbox/Models/TestModel.cs rename to src/Mvc/samples/MvcSandbox/Models/TestModel.cs diff --git a/samples/MvcSandbox/MvcSandbox.csproj b/src/Mvc/samples/MvcSandbox/MvcSandbox.csproj similarity index 100% rename from samples/MvcSandbox/MvcSandbox.csproj rename to src/Mvc/samples/MvcSandbox/MvcSandbox.csproj diff --git a/samples/MvcSandbox/Pages/PagesHome.cshtml b/src/Mvc/samples/MvcSandbox/Pages/PagesHome.cshtml similarity index 100% rename from samples/MvcSandbox/Pages/PagesHome.cshtml rename to src/Mvc/samples/MvcSandbox/Pages/PagesHome.cshtml diff --git a/samples/MvcSandbox/Pages/_ViewImports.cshtml b/src/Mvc/samples/MvcSandbox/Pages/_ViewImports.cshtml similarity index 100% rename from samples/MvcSandbox/Pages/_ViewImports.cshtml rename to src/Mvc/samples/MvcSandbox/Pages/_ViewImports.cshtml diff --git a/samples/MvcSandbox/Pages/_ViewStart.cshtml b/src/Mvc/samples/MvcSandbox/Pages/_ViewStart.cshtml similarity index 100% rename from samples/MvcSandbox/Pages/_ViewStart.cshtml rename to src/Mvc/samples/MvcSandbox/Pages/_ViewStart.cshtml diff --git a/samples/MvcSandbox/Startup.cs b/src/Mvc/samples/MvcSandbox/Startup.cs similarity index 100% rename from samples/MvcSandbox/Startup.cs rename to src/Mvc/samples/MvcSandbox/Startup.cs diff --git a/samples/MvcSandbox/Views/Home/Index.cshtml b/src/Mvc/samples/MvcSandbox/Views/Home/Index.cshtml similarity index 100% rename from samples/MvcSandbox/Views/Home/Index.cshtml rename to src/Mvc/samples/MvcSandbox/Views/Home/Index.cshtml diff --git a/samples/MvcSandbox/Views/Shared/_Layout.cshtml b/src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml similarity index 100% rename from samples/MvcSandbox/Views/Shared/_Layout.cshtml rename to src/Mvc/samples/MvcSandbox/Views/Shared/_Layout.cshtml diff --git a/samples/MvcSandbox/Views/_ViewImports.cshtml b/src/Mvc/samples/MvcSandbox/Views/_ViewImports.cshtml similarity index 100% rename from samples/MvcSandbox/Views/_ViewImports.cshtml rename to src/Mvc/samples/MvcSandbox/Views/_ViewImports.cshtml diff --git a/samples/MvcSandbox/Views/_ViewStart.cshtml b/src/Mvc/samples/MvcSandbox/Views/_ViewStart.cshtml similarity index 100% rename from samples/MvcSandbox/Views/_ViewStart.cshtml rename to src/Mvc/samples/MvcSandbox/Views/_ViewStart.cshtml diff --git a/src/Directory.Build.props b/src/Mvc/src/Directory.Build.props similarity index 100% rename from src/Directory.Build.props rename to src/Mvc/src/Directory.Build.props diff --git a/src/GetDocumentInsider/AnsiConsole.cs b/src/Mvc/src/GetDocumentInsider/AnsiConsole.cs similarity index 100% rename from src/GetDocumentInsider/AnsiConsole.cs rename to src/Mvc/src/GetDocumentInsider/AnsiConsole.cs diff --git a/src/GetDocumentInsider/AnsiConstants.cs b/src/Mvc/src/GetDocumentInsider/AnsiConstants.cs similarity index 100% rename from src/GetDocumentInsider/AnsiConstants.cs rename to src/Mvc/src/GetDocumentInsider/AnsiConstants.cs diff --git a/src/GetDocumentInsider/AnsiTextWriter.cs b/src/Mvc/src/GetDocumentInsider/AnsiTextWriter.cs similarity index 100% rename from src/GetDocumentInsider/AnsiTextWriter.cs rename to src/Mvc/src/GetDocumentInsider/AnsiTextWriter.cs diff --git a/src/GetDocumentInsider/CommandException.cs b/src/Mvc/src/GetDocumentInsider/CommandException.cs similarity index 100% rename from src/GetDocumentInsider/CommandException.cs rename to src/Mvc/src/GetDocumentInsider/CommandException.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandArgument.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandLineApplication.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandLineApplicationExtensions.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandOption.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandOption.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandOptionType.cs diff --git a/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs b/src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs similarity index 100% rename from src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs rename to src/Mvc/src/GetDocumentInsider/CommandLineUtils/CommandParsingException.cs diff --git a/src/GetDocumentInsider/Commands/CommandBase.cs b/src/Mvc/src/GetDocumentInsider/Commands/CommandBase.cs similarity index 100% rename from src/GetDocumentInsider/Commands/CommandBase.cs rename to src/Mvc/src/GetDocumentInsider/Commands/CommandBase.cs diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommand.cs b/src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommand.cs similarity index 100% rename from src/GetDocumentInsider/Commands/GetDocumentCommand.cs rename to src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommand.cs diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs b/src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs similarity index 100% rename from src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs rename to src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommandContext.cs diff --git a/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs b/src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs similarity index 100% rename from src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs rename to src/Mvc/src/GetDocumentInsider/Commands/GetDocumentCommandWorker.cs diff --git a/src/GetDocumentInsider/Commands/HelpCommandBase.cs b/src/Mvc/src/GetDocumentInsider/Commands/HelpCommandBase.cs similarity index 100% rename from src/GetDocumentInsider/Commands/HelpCommandBase.cs rename to src/Mvc/src/GetDocumentInsider/Commands/HelpCommandBase.cs diff --git a/src/GetDocumentInsider/Commands/ProjectCommandBase.cs b/src/Mvc/src/GetDocumentInsider/Commands/ProjectCommandBase.cs similarity index 100% rename from src/GetDocumentInsider/Commands/ProjectCommandBase.cs rename to src/Mvc/src/GetDocumentInsider/Commands/ProjectCommandBase.cs diff --git a/src/GetDocumentInsider/GetDocumentInsider.csproj b/src/Mvc/src/GetDocumentInsider/GetDocumentInsider.csproj similarity index 100% rename from src/GetDocumentInsider/GetDocumentInsider.csproj rename to src/Mvc/src/GetDocumentInsider/GetDocumentInsider.csproj diff --git a/src/GetDocumentInsider/ProductInfo.cs b/src/Mvc/src/GetDocumentInsider/ProductInfo.cs similarity index 100% rename from src/GetDocumentInsider/ProductInfo.cs rename to src/Mvc/src/GetDocumentInsider/ProductInfo.cs diff --git a/src/GetDocumentInsider/Program.cs b/src/Mvc/src/GetDocumentInsider/Program.cs similarity index 100% rename from src/GetDocumentInsider/Program.cs rename to src/Mvc/src/GetDocumentInsider/Program.cs diff --git a/src/GetDocumentInsider/Properties/Resources.Designer.cs b/src/Mvc/src/GetDocumentInsider/Properties/Resources.Designer.cs similarity index 100% rename from src/GetDocumentInsider/Properties/Resources.Designer.cs rename to src/Mvc/src/GetDocumentInsider/Properties/Resources.Designer.cs diff --git a/src/GetDocumentInsider/Reporter.cs b/src/Mvc/src/GetDocumentInsider/Reporter.cs similarity index 100% rename from src/GetDocumentInsider/Reporter.cs rename to src/Mvc/src/GetDocumentInsider/Reporter.cs diff --git a/src/GetDocumentInsider/Resources.resx b/src/Mvc/src/GetDocumentInsider/Resources.resx similarity index 100% rename from src/GetDocumentInsider/Resources.resx rename to src/Mvc/src/GetDocumentInsider/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionDescriptorProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ActionInvokerProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionDescriptorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/IActionInvokerProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Abstractions/ParameterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionConstraintProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/ActionSelectorCandidate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionConstraints/IActionConstraintProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ActionContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescription.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiDescriptionProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterDescription.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiParameterRouteInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiRequestFormat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseFormat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/ApiResponseType.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ApiExplorer/IApiDescriptionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Authorization/IAllowAnonymousFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutedContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ActionExecutionDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/AuthorizationFilterContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ExceptionContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/FilterProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IActionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAlwaysRunResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncActionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAlwaysRunResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncExceptionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResourceFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAsyncResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IExceptionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterContainer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IFilterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IOrderedFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResourceFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/IResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutedContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResourceExecutionDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutedContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Filters/ResultExecutionDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/FormatterCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IInputFormatterExceptionPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/IOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterExceptionPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/InputFormatterResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterCanWriteContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Formatters/OutputFormatterWriteContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/IUrlHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Microsoft.AspNetCore.Mvc.Abstractions.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/BindingSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/CompositeBindingSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/EnumGroupAndName.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBinderTypeProviderMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IBindingSourceMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IModelNameProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IPropertyFilterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IRequestPredicateProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/IValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelBindingMessageProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataIdentity.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Metadata/ModelMetadataKind.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBinderProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelBindingResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelError.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelErrorCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelPropertyCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelStateEntry.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ModelValidationState.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/TooManyModelErrorsException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientModelValidationContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ClientValidatorProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IPropertyValidationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/IValidationStrategy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationContextBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidationResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ModelValidatorProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationEntry.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidationStateEntry.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/Validation/ValidatorItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderFactoryContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/ModelBinding/ValueProviderResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/AttributeRouteInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlActionContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/Routing/UrlRouteContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Abstractions/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AttributesShouldNotBeAppliedToPageModelAnalyzer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/AvoidHtmlPartialAnalyzer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/CodeAnalysisExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/DiagnosticDescriptors.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Microsoft.AspNetCore.Mvc.Analyzers.nuspec diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/MvcFacts.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/SymbolNames.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/TopLevelParameterNameAnalyzer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeatureAnalyzerBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Analyzers/ViewFeaturesAnalyzerContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ActualApiResponseMetadataFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixAction.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/AddResponseTypeAttributeCodeFixProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiActionsDoNotRequireExplicitModelValidationCodeFixProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerFacts.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiControllerSymbolCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiConventionAnalyzer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiDiagnosticDescriptors.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/ApiSymbolNames.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/DeclaredApiResponseMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Microsoft.AspNetCore.Mvc.Api.Analyzers.nuspec diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiConventionMatcher.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Api.Analyzers/SymbolApiResponseMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiDescriptionGroupCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiParameterContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/ApiResponseTypeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DefaultApiDescriptionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/DependencyInjection/MvcApiExplorerMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/IApiDescriptionGroupCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Microsoft.AspNetCore.Mvc.ApiExplorer.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ApiExplorer/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptVerbsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedAtRouteResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AcceptedResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionConstraints/ActionMethodSelectorAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionContextAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionNameAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ActionResultOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AntiforgeryValidationFailedResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiBehaviorOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiControllerAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionMethodAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiConventionTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionMatcher.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionNameMatchBehavior.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/ApiConventionTypeMatchBehavior.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDefaultResponseMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionGroupNameProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiDescriptionVisibilityProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestFormatMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiRequestMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorer/IApiResponseTypeMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApiExplorerSettingsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ActionModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiBehaviorApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiConventionApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiExplorerModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApiVisibilityConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ApplicationModelProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/AttributeRouteModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ClientErrorResultFilterConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ConsumesConstraintForFormFileParameterConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ControllerModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IActionModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApiExplorerModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IBindingModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ICommonModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IControllerModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IFilterModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelBaseConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IParameterModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/IPropertyModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InferParameterBindingInfoConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/InvalidModelStateFilterConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/ParameterModelBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/PropertyModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/RouteTokenTransformerConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationModels/SelectorModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationAssembliesProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPart.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ApplicationPartManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/AssemblyPart.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/DefaultApplicationPartFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationFeatureProviderOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/IApplicationPartTypeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ICompilationReferencesProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/NullApplicationPartFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/ProvideApplicationPartFactoryAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ApplicationParts/RelatedAssemblyAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/AreaAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AllowAnonymousFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Authorization/AuthorizeFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BadRequestResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertiesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/BindPropertyAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcApplicationBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcAreaRouteBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Builder/MvcEndpointInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CacheProfile.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ChallengeResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ClientErrorData.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CompatibilityVersion.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConflictResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ConsumesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ContentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ControllerContextAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActionDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerBoundPropertyDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ControllerParameterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/DefaultControllerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/IControllerFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Controllers/ServiceBasedControllerActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedAtRouteResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/CreatedResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DefaultApiConventions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DefaultObjectValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/ApplicationModelConventionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/IMvcCoreBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DependencyInjection/MvcCoreServiceCollectionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/DisableRequestSizeLimitAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/EmptyResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileContentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FileStreamResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ActionFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ExceptionFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/FilterScope.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/MiddlewareFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Filters/ResultFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ForbidResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FormatFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/FormatterMappings.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/HttpNoContentOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/InputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaType.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/MediaTypeCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/OutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StreamOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/StringOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Formatters/TextOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromBodyAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromFormAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromHeaderAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromQueryAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromRouteAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/FromServicesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpDeleteAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpGetAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpHeadAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpOptionsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPatchAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPostAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/HttpPutAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IApiBehaviorMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IDesignTimeMvcBuilderConfiguration.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestFormLimitsPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/IRequestSizePolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionContextAccessor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionDescriptorCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultObjectValueAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ActionResultStatusCodeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ClientErrorResultFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/CompatibilitySwitch.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ContentResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultActionDescriptorCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultOutputFormatterSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/DefaultStatusCodeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileContentResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileResultExecutorBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/FileStreamResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionContextAccessor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorChangeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionDescriptorCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionInvokerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionResultTypeMapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IActionSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IAntiforgeryValidationFailedResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IClientErrorFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ICompatibilitySwitch.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IConvertToActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpRequestStreamReaderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IHttpResponseStreamWriterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IParameterInfoParameterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IPropertyInfoParameterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/IStatusCodeActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/LocalRedirectResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ModelStateInvalidFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcCoreMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/MvcOptionsConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/NullableCompatibilitySwitch.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ObjectResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/OutputFormatterSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/PhysicalFileResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/ProblemDetailsClientErrorFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToActionResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToPageResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/RedirectToRouteResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Infrastructure/VirtualFileResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AcceptHeaderParser.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionAttributeRouteModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionInvokerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionMethodExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionResultTypeMapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AmbiguousActionException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiBehaviorOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApiDescriptionActionData.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ApplicationModelConventions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRoute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AttributeRouting.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/AuthorizationApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ClientValidatorCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionDescriptorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerCacheEntry.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerBinderDelegateProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerResultFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/CopyOnWriteList.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultActionConstraintProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultBindingMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCollectionValidationStrategy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultComplexObjectValidationStrategy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultCompositeMetadataDetailsProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultControllerPropertyActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultFilterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultModelBindingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DefaultValidationMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/DisableRequestSizeLimitFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ElementalValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ExplicitIndexCollectionValidationStrategy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterCursorItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterDescriptorOrderComparer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterFactoryResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpMethodActionConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpParseResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/HttpTokenParsingRules.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IConsumesActionConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IControllerPropertyActivatorFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IFormatFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IMiddlewareFilterFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/IResponseCacheFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ITypeActivatorCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MediaTypeSegmentWithQuality.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpRequestStreamReaderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MemoryPoolHttpResponseStreamWriterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterBuilderStartupFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterConfigurationProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MiddlewareFilterFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcAttributeRouteHandler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreDiagnosticSourceExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreRouteOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointDataSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcEndpointInvokerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcMarkerService.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcRouteHandler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NoOpBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NonDisposableStream.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NormalizedRouteValue.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/NullRouter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ParameterDefaultValues.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PlaceholderBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PrefixContainer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/PropertyValueSetter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ReferenceEqualityComparer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestFormLimitsFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RequestSizeLimitFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseCacheFilterExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResponseContentTypeHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/RoutePatternWriter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ShortFormDictionaryValidationStrategy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/TypeActivatorCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ValidatorCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Internal/ViewEnginePath.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/LocalRedirectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Microsoft.AspNetCore.Mvc.Core.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinderAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindNeverAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindRequiredAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ArrayModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BinderTypeModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/BodyModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ByteArrayModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CancellationTokenModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/CollectionModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ComplexTypeModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DecimalModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DictionaryModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/DoubleModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/EnumTypeModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FloatingPointTypeModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormCollectionModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/FormFileModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/HeaderModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/KeyValuePairModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/ServicesModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Binders/SimpleTypeModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehavior.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingBehaviorAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/BindingSourceValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/CompositeValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/DefaultPropertyFilterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/EmptyModelMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/FormValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IBindingSourceValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ICollectionModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IEnumerableValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IKeyRewriterValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/IModelBinderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ModelBindingHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Internal/ValidationStack.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryFormValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryKeyValuePairNormalizer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryQueryStringValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/JQueryValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingMetadataProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/BindingSourceMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultMetadataDetails.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelBindingMessageProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DefaultModelMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/DisplayMetadataProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ExcludeBindingMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/HasValidatorsValidationMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IBindingMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ICompositeMetadataDetailsProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IDisplayMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IMetadataDetailsProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/IValidationMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/MetadataDetailsProviderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ModelAttributes.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Metadata/ValidationMetadataProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderFactoryContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelBinderProviderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelMetadataProviderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ModelNames.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ObjectModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ParameterBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/QueryStringValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/RouteValueProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/SuppressChildValidationMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/UnsupportedContentTypeFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/CompositeModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/DefaultModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IMetadataBasedModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/IObjectModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ModelValidatorProviderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidateNeverAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/Validation/ValidationVisitor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelBinding/ValueProviderFactoryExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ModelMetadataTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/MvcOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NoContentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonActionAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonControllerAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NonViewComponentAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/NotFoundResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/OkResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/PhysicalFileResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProblemDetails.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesDefaultResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesErrorResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ProducesResponseTypeAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToActionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToPageResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RedirectToRouteResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestFormLimitsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequestSizeLimitAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RequireHttpsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ResponseCacheLocation.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/RouteAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/ControllerLinkGeneratorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/EndpointRoutingUrlHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/HttpMethodAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IActionHttpMethodProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IConsumesMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteTemplateProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IRouteValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/IUrlHelperFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/KnownRouteValueConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/PageLinkGeneratorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/RouteValueAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/Routing/UrlHelperFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SerializableError.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ServiceFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignInResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/SignOutResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/StatusCodeResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/TypeFilterAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnauthorizedResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityObjectResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnprocessableEntityResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UnsupportedMediaTypeResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/UrlHelperExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ValidationProblemDetails.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/ViewFeatures/IKeepTempDataResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/VirtualFileResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Core/breakingchanges.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/CorsAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/DependencyInjection/MvcCorsMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsAuthorizationFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsHttpMethodActionConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/CorsLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/DisableCorsAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Internal/ICorsAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Microsoft.AspNetCore.Mvc.Cors.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Cors/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/AttributeAdapterBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DataAnnotationsModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/DependencyInjection/MvcDataAnnotationsMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/HiddenInputAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/IValidationAttributeAdapterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/CompareAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsLocalizationServices.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataAnnotationsModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DataTypeAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/DefaultClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/FileExtensionsAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MaxLengthAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MinLengthAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsLocalizationOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/MvcDataAnnotationsMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/NumericClientModelValidatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RangeAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RegularExpressionAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/RequiredAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/StringLengthAttributeAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Internal/ValidatableObjectAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Microsoft.AspNetCore.Mvc.DataAnnotations.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/MvcDataAnnotationsLocalizationOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterOfTAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationAttributeAdapterProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/ValidationProviderAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.DataAnnotations/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/DependencyInjection/MvcJsonMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonArrayPool.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/JsonSerializerObjectPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MediaTypeHeaderValues.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Internal/MvcJsonMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonPatchOperationsArrayProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/JsonSerializerSettingsProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Microsoft.AspNetCore.Mvc.Formatters.Json.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/MvcJsonOptionsExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Json/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerable.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DelegatingEnumerator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/DependencyInjection/MvcXmlMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/EnumerableWrapperProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IUnwrappable.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/IWrapperProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/FormattingUtilities.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Internal/MediaTypeHeaderValues.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/LoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Microsoft.AspNetCore.Mvc.Formatters.Xml.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ModelBinding/DataMemberRequiredBindingMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/MvcXmlOptionsConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetails21Wrapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ProblemDetailsWrapperProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/SerializableErrorWrapperProviderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetails21Wrapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/ValidationProblemDetailsWrapper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/WrapperProviderFactoriesExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlDataContractSerializerOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerInputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/XmlSerializerOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Formatters.Xml/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/DependencyInjection/MvcLocalizationMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/HtmlLocalizerOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IHtmlLocalizerOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/IViewLocalizer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/LocalizedHtmlString.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Microsoft.AspNetCore.Mvc.Localization.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/MvcLocalizationServices.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/ViewLocalizer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Localization/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyApplicationPartFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/CompiledRazorAssemblyPart.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/IRazorCompiledItemProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ApplicationParts/RazorCompiledItemFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompilationFailedException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/CompiledViewDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilationMemoryCacheProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompiler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/IViewCompilerProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/MetadataReferenceFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorReferenceManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RazorViewCompilationMemoryCacheProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/RoslynCompilationContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Compilation/ViewsFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/DependencyInjection/MvcRazorMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/HelperResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IModelTypeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPage.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorPageFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IRazorViewEngine.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ITagHelperInitializerOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/IViewLocationExpander.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/CryptographyAlgorithms.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/DefaultFileVersionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Infrastructure/TagHelperMemoryCacheProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CSharpCompiler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ChecksumValidator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/CompilationFailedExceptionFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorPageFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorReferenceManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultRazorViewEngineFileProviderAccessor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/DefaultTagHelperFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ExpressionRewriter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProject.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/FileProviderRazorProjectItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/IRazorViewEngineFileProviderAccessor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/LazyMetadataReferenceFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorDiagnosticSourceExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/MvcRazorMvcViewOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorInjectAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorPagePropertyActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompiler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/RazorViewCompilerProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ServiceBasedTagHelperActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/SymbolsUtility.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelperComponentManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/TagHelpersAsServices.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheKey.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewLocationCacheResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Internal/ViewPath.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpander.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/LanguageViewLocationExpanderFormat.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Microsoft.AspNetCore.Mvc.Razor.nuspec diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPage.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageFactoryResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorPageResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorView.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngine.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RazorViewEngineOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/RenderAsyncDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelperInitializerOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/BodyTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/HeadTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentManager.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/ITagHelperComponentPropertyActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentPropertyActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperComponentTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/TagHelperFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/TagHelpers/UrlResolutionTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/ViewLocationExpanderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Razor/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/DefaultPageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageHandlerModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageApplicationModelProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageHandlerModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageParameterModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PagePropertyModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteModelProviderContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageRouteTransformerConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/AutoValidateAntiforgeryPageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/CompiledPageActionDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IAsyncPageFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/IPageFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutedContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerExecutionDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Filters/PageHandlerSelectedContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/IPageModelFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/DefaultPageModelFactoryProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandleOptionsRequestsPageFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerMethodDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/HandlerParameterDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageHandlerMethodSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/IPageLoader.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageArgumentBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageBoundPropertyDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageDirectiveFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageModelAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageViewLocationExpander.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAdapter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/RazorPageAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/ServiceBasedPageModelActivatorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/AuthorizationPageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageActionDescriptorBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/CompiledPageRouteModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageArgumentBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageHandlerMethodSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ExecutorFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/MvcRazorPagesDiagnosticSourceExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionDescriptorChangeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerCacheEntry.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageActionInvokerProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageBinderFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerBinderDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerExecutorDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerPageFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageHandlerResultFIlter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageSaveTempDataPropertyFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageViewDataAttributeFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesRazorViewEngineOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorProjectPageRouteModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ResponseCacheFilterApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/TempDataFilterPageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/ViewDataAttributePageApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Microsoft.AspNetCore.Mvc.RazorPages.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/NonHandlerAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Page.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageActionDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageContextAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageModel.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/PageResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptionsConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.RazorPages/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/AnchorTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/CacheTagKey.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperFormattingContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperService.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/DistributedCacheTagHelperStorage.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperService.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Cache/IDistributedCacheTagHelperStorage.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/CacheTagHelperOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DependencyInjection/TagHelperExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/DistributedCacheTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/EnvironmentTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormActionTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/FormTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ImageTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/InputTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/AttributeMatcher.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/AttributeMatcher.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/AttributeMatcher.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/AttributeMatcher.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CacheTagHelperMemoryCacheFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CacheTagHelperMemoryCacheFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CacheTagHelperMemoryCacheFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CacheTagHelperMemoryCacheFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CurrentValues.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CurrentValues.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CurrentValues.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/CurrentValues.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingDirectory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingDirectory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingDirectory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingDirectory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingFile.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingFile.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingFile.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/FileProviderGlobbingFile.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/GlobbingUrlBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/JavaScriptResources.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/JavaScriptResources.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/JavaScriptResources.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/JavaScriptResources.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/ModeAttributesOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/MvcTagHelpersLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/MvcTagHelpersLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/MvcTagHelpersLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Internal/MvcTagHelpersLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/LabelTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/LinkTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Microsoft.AspNetCore.Mvc.TagHelpers.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/OptionTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/OptionTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/OptionTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/OptionTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/PartialTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/RenderAtEndOfFormTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Rendering/ValidationSummary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Rendering/ValidationSummary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Rendering/ValidationSummary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Rendering/ValidationSummary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ScriptTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/SelectTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/TagHelperOutputExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/TextAreaTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/TextAreaTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/TextAreaTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/TextAreaTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationMessageTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationSummaryTagHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationSummaryTagHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationSummaryTagHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/ValidationSummaryTagHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/_grunt.readme b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_grunt.readme similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/_grunt.readme rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_grunt.readme diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/_gruntfile.js b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_gruntfile.js similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/_gruntfile.js rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_gruntfile.js diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/_package.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_package.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/_package.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/_package.json diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/compiler/resources/LinkTagHelper_FallbackJavaScript.js b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/compiler/resources/LinkTagHelper_FallbackJavaScript.js similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/compiler/resources/LinkTagHelper_FallbackJavaScript.js rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/compiler/resources/LinkTagHelper_FallbackJavaScript.js diff --git a/src/Microsoft.AspNetCore.Mvc.TagHelpers/js/LinkTagHelper_FallbackJavaScript.js b/src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/js/LinkTagHelper_FallbackJavaScript.js similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.TagHelpers/js/LinkTagHelper_FallbackJavaScript.js rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.TagHelpers/js/LinkTagHelper_FallbackJavaScript.js diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/CookieContainerHandler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Handlers/RedirectHandler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Microsoft.AspNetCore.Mvc.Testing.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryClientOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/WebApplicationFactoryContentRootAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets b/src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.Testing/build/netstandard2.0/Microsoft.AspNetCore.Mvc.Testing.targets diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/AutoValidateAntiforgeryTokenAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Controller.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/CookieTempDataProviderOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewComponentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewContextAware.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewContextAware.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewContextAware.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IViewContextAware.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IgnoreAntiforgeryTokenAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IgnoreAntiforgeryTokenAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/IgnoreAntiforgeryTokenAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/IgnoreAntiforgeryTokenAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ArrayPoolBufferSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ArrayPoolBufferSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ArrayPoolBufferSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ArrayPoolBufferSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/AutoValidateAntiforgeryTokenAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/AutoValidateAntiforgeryTokenAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/AutoValidateAntiforgeryTokenAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/AutoValidateAntiforgeryTokenAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CharArrayBufferSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CharArrayBufferSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CharArrayBufferSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/CharArrayBufferSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerSaveTempDataPropertyFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilterFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilterFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilterFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ControllerViewDataAttributeFilterFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultDisplayTemplates.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultDisplayTemplates.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultDisplayTemplates.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultDisplayTemplates.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultEditorTemplates.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultEditorTemplates.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultEditorTemplates.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DefaultEditorTemplates.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DynamicViewData.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DynamicViewData.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DynamicViewData.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/DynamicViewData.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionMetadataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionTextCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionTextCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionTextCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ExpressionTextCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/FormatWeekHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/FormatWeekHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/FormatWeekHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/FormatWeekHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/HtmlAttributePropertyHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/HtmlAttributePropertyHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/HtmlAttributePropertyHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/HtmlAttributePropertyHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ICharBufferSource.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ICharBufferSource.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ICharBufferSource.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ICharBufferSource.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ISaveTempDataCallback.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ISaveTempDataCallback.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ISaveTempDataCallback.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ISaveTempDataCallback.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewBufferScope.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewBufferScope.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewBufferScope.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewBufferScope.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewDataValuesProviderFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewDataValuesProviderFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewDataValuesProviderFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/IViewDataValuesProviderFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/LifecycleProperty.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/LifecycleProperty.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/LifecycleProperty.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/LifecycleProperty.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MemoryPoolViewBufferScope.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MemoryPoolViewBufferScope.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MemoryPoolViewBufferScope.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MemoryPoolViewBufferScope.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesDiagnosticSourceExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesDiagnosticSourceExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesDiagnosticSourceExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesDiagnosticSourceExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesLoggerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesLoggerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesLoggerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewFeaturesLoggerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/MvcViewOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NameAndIdProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NameAndIdProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NameAndIdProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NameAndIdProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NullView.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NullView.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NullView.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/NullView.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedBufferedTextWriter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/PagedCharBuffer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/SaveTempDataPropertyFilterBase.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataMvcOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataMvcOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataMvcOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataMvcOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataSerializer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataSerializer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataSerializer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/TempDataSerializer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidateAntiforgeryTokenAuthorizationFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidationHelpers.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidationHelpers.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidationHelpers.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ValidationHelpers.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBuffer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferPage.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferTextWriter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferTextWriter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferTextWriter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferTextWriter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewBufferValue.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributeApplicationModelProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributeApplicationModelProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributeApplicationModelProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributeApplicationModelProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributePropertyProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributePropertyProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributePropertyProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataAttributePropertyProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Internal/ViewDataDictionaryFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Microsoft.AspNetCore.Mvc.ViewFeatures.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ModelStateDictionaryExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/MvcViewOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/PartialViewResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/RemoteAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/RemoteAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/RemoteAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/RemoteAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/FormMethod.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/FormMethod.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/FormMethod.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/FormMethod.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/Html5DateRenderingMode.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/Html5DateRenderingMode.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/Html5DateRenderingMode.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/Html5DateRenderingMode.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayNameExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayNameExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayNameExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperDisplayNameExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperEditorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperEditorExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperEditorExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperEditorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperFormExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperFormExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperFormExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperFormExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperInputExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperInputExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperInputExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperInputExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLabelExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLabelExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLabelExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLabelExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLinkExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLinkExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLinkExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperLinkExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperNameExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperNameExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperNameExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperNameExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperPartialExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperPartialExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperPartialExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperPartialExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperSelectExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperSelectExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperSelectExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperSelectExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValidationExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValidationExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValidationExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValidationExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValueExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValueExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValueExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/HtmlHelperValueExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelperOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelperOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelperOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IHtmlHelperOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IJsonHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IJsonHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IJsonHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/IJsonHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MultiSelectList.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MultiSelectList.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MultiSelectList.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MultiSelectList.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MvcForm.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MvcForm.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MvcForm.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/MvcForm.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectList.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectList.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectList.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectList.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListGroup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListGroup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListGroup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListGroup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListItem.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListItem.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListItem.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/SelectListItem.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagRenderMode.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagRenderMode.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagRenderMode.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/TagRenderMode.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewComponentHelperExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Rendering/ViewContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/SkipStatusCodePagesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/SkipStatusCodePagesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/SkipStatusCodePagesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/SkipStatusCodePagesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/TempDataAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/TempDataAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/TempDataAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/TempDataAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ValidateAntiForgeryTokenAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponent.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponent.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponent.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponent.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ContentViewComponentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ContentViewComponentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ContentViewComponentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ContentViewComponentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentDescriptorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentInvokerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/DefaultViewComponentSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/HtmlContentViewComponentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/HtmlContentViewComponentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/HtmlContentViewComponentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/HtmlContentViewComponentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentDescriptorProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvokerFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvokerFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvokerFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentInvokerFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/IViewComponentSelector.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ServiceBasedViewComponentActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContextAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContextAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentContextAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentConventions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentConventions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentConventions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptorCollection.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptorCollection.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptorCollection.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentDescriptorCollection.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentFeatureProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentInvokerCache.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentInvokerCache.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentInvokerCache.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewComponentInvokerCache.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewComponents/ViewViewComponentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewDataAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewDataAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewDataAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewDataAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/CompositeViewEngine.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ICompositeViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ICompositeViewEngine.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ICompositeViewEngine.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ICompositeViewEngine.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IView.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IView.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IView.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IView.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/IViewEngine.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewEngines/ViewEngineResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AntiforgeryExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AntiforgeryExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AntiforgeryExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AntiforgeryExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/AttributeDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CachedExpressionCompiler.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CookieTempDataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CookieTempDataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CookieTempDataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/CookieTempDataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGenerator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultHtmlGeneratorExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/DefaultValidationHtmlAttributeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/FormContext.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/HtmlHelperOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IAntiforgeryPolicy.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IAntiforgeryPolicy.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IAntiforgeryPolicy.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IAntiforgeryPolicy.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IFileVersionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IHtmlGenerator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IModelExpressionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IModelExpressionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IModelExpressionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/IModelExpressionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionaryFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionaryFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionaryFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataDictionaryFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ITempDataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/InputType.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/InputType.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/InputType.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/InputType.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/JsonHelper.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKey.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MemberExpressionCacheKeyComparer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExplorerExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpression.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpression.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpression.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpression.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelExpressionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelMetadataProviderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelMetadataProviderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelMetadataProviderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ModelMetadataProviderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/MvcViewOptionsConfigureCompatibilityOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/PartialViewResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SaveTempDataAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SessionStateTempDataProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SessionStateTempDataProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SessionStateTempDataProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/SessionStateTempDataProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/StringHtmlContent.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/StringHtmlContent.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/StringHtmlContent.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/StringHtmlContent.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionaryFactory.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionaryFactory.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionaryFactory.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TempDataDictionaryFactory.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateBuilder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TemplateRenderer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueDelegate.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueDelegate.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueDelegate.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueDelegate.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/TryGetValueProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ValidationHtmlAttributeProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ValidationHtmlAttributeProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ValidationHtmlAttributeProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ValidationHtmlAttributeProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewComponentResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewComponentResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewComponentResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewComponentResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewContextAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewContextAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewContextAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewContextAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionary.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionary.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionary.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionary.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryControllerPropertyActivator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryControllerPropertyActivator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryControllerPropertyActivator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryControllerPropertyActivator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryOfT.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryOfT.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryOfT.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataDictionaryOfT.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataEvaluator.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataEvaluator.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataEvaluator.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataEvaluator.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewDataInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewFeatures/ViewResultExecutor.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/ViewResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.ViewFeatures/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ApiController.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ApiController.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ApiController.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ApiController.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/BadRequestErrorMessageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/BadRequestErrorMessageResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/BadRequestErrorMessageResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/BadRequestErrorMessageResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ConflictResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ConflictResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ConflictResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ConflictResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiActionConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiActionConventions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiActionConventions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiActionConventions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiOverloading.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiOverloading.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiOverloading.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiOverloading.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiParameterConventions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiParameterConventions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiParameterConventions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiParameterConventions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiRoutes.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiRoutes.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiRoutes.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/IUseWebApiRoutes.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiActionConventionsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiActionConventionsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiActionConventionsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiActionConventionsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiOverloadingAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiOverloadingAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiOverloadingAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiOverloadingAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiParameterConventionsAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiParameterConventionsAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiParameterConventionsAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiParameterConventionsAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiRoutesAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiRoutesAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiRoutesAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/UseWebApiRoutesAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiActionConventionsApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiOverloadingApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiOverloadingApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiOverloadingApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiOverloadingApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiParameterConventionsApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiRoutesApplicationModelConvention.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiRoutesApplicationModelConvention.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiRoutesApplicationModelConvention.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Conventions/WebApiRoutesApplicationModelConvention.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ExceptionResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ExceptionResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ExceptionResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ExceptionResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/FormDataCollectionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Formatters/HttpResponseMessageOutputFormatter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpError.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpError.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpError.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpError.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpErrorKeys.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpErrorKeys.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpErrorKeys.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpErrorKeys.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageHttpContextExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinder.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinder.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinder.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinder.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinderProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinderProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinderProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/HttpRequestMessageModelBinderProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpRequestMessage/IHttpRequestMessageFeature.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseException.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseException.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseException.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseException.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseExceptionActionFilter.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseExceptionActionFilter.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseExceptionActionFilter.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/HttpResponseExceptionActionFilter.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InternalServerErrorResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InternalServerErrorResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InternalServerErrorResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InternalServerErrorResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InvalidModelStateResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InvalidModelStateResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InvalidModelStateResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/InvalidModelStateResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Microsoft.AspNetCore.Mvc.WebApiCompatShim.csproj diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/NegotiatedContentResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/NegotiatedContentResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/NegotiatedContentResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/NegotiatedContentResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/OverloadActionConstraint.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/OverloadActionConstraint.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/OverloadActionConstraint.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/OverloadActionConstraint.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/FromUriAttribute.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ParameterBinding/IOptionalBinderMetadata.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Properties/Resources.Designer.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Resources.resx b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Resources.resx similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Resources.resx rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Resources.resx diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ResponseMessageResult.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ResponseMessageResult.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ResponseMessageResult.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/ResponseMessageResult.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Routing/WebApiCompatShimRouteBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Routing/WebApiCompatShimRouteBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Routing/WebApiCompatShimRouteBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/Routing/WebApiCompatShimRouteBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimMvcBuilderExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimMvcBuilderExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimMvcBuilderExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimMvcBuilderExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptions.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/WebApiCompatShimOptionsSetup.cs diff --git a/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc.WebApiCompatShim/baseline.netcore.json diff --git a/src/Microsoft.AspNetCore.Mvc/DesignTimeMvcServiceCollectionProvider.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc/DesignTimeMvcServiceCollectionProvider.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc/DesignTimeMvcServiceCollectionProvider.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc/DesignTimeMvcServiceCollectionProvider.cs diff --git a/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj b/src/Mvc/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj similarity index 100% rename from src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj rename to src/Mvc/src/Microsoft.AspNetCore.Mvc/Microsoft.AspNetCore.Mvc.csproj diff --git a/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc/MvcServiceCollectionExtensions.cs diff --git a/src/Microsoft.AspNetCore.Mvc/Properties/AssemblyInfo.cs b/src/Mvc/src/Microsoft.AspNetCore.Mvc/Properties/AssemblyInfo.cs similarity index 100% rename from src/Microsoft.AspNetCore.Mvc/Properties/AssemblyInfo.cs rename to src/Mvc/src/Microsoft.AspNetCore.Mvc/Properties/AssemblyInfo.cs diff --git a/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json b/src/Mvc/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json similarity index 100% rename from src/Microsoft.AspNetCore.Mvc/baseline.netcore.json rename to src/Mvc/src/Microsoft.AspNetCore.Mvc/baseline.netcore.json diff --git a/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/DownloadFile.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetCurrentItems.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetFileReferenceMetadata.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetProjectReferenceMetadata.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/GetUriReferenceMetadata.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/MetadataSerializer.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.csproj diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Microsoft.Extensions.ApiDescription.Design.nuspec diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Properties/Resources.Designer.cs diff --git a/src/Microsoft.Extensions.ApiDescription.Design/Resources.resx b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Resources.resx similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/Resources.resx rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/Resources.resx diff --git a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.props diff --git a/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.targets b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.targets similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.targets rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/build/Microsoft.Extensions.ApiDescription.Design.targets diff --git a/src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets b/src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets similarity index 100% rename from src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets rename to src/Mvc/src/Microsoft.Extensions.ApiDescription.Design/buildMultiTargeting/Microsoft.Extensions.ApiDescription.Design.targets diff --git a/src/dotnet-getdocument/Commands/InvokeCommand.cs b/src/Mvc/src/dotnet-getdocument/Commands/InvokeCommand.cs similarity index 100% rename from src/dotnet-getdocument/Commands/InvokeCommand.cs rename to src/Mvc/src/dotnet-getdocument/Commands/InvokeCommand.cs diff --git a/src/dotnet-getdocument/Exe.cs b/src/Mvc/src/dotnet-getdocument/Exe.cs similarity index 100% rename from src/dotnet-getdocument/Exe.cs rename to src/Mvc/src/dotnet-getdocument/Exe.cs diff --git a/src/dotnet-getdocument/Program.cs b/src/Mvc/src/dotnet-getdocument/Program.cs similarity index 100% rename from src/dotnet-getdocument/Program.cs rename to src/Mvc/src/dotnet-getdocument/Program.cs diff --git a/src/dotnet-getdocument/Project.cs b/src/Mvc/src/dotnet-getdocument/Project.cs similarity index 100% rename from src/dotnet-getdocument/Project.cs rename to src/Mvc/src/dotnet-getdocument/Project.cs diff --git a/src/dotnet-getdocument/ProjectOptions.cs b/src/Mvc/src/dotnet-getdocument/ProjectOptions.cs similarity index 100% rename from src/dotnet-getdocument/ProjectOptions.cs rename to src/Mvc/src/dotnet-getdocument/ProjectOptions.cs diff --git a/src/dotnet-getdocument/Properties/Resources.Designer.cs b/src/Mvc/src/dotnet-getdocument/Properties/Resources.Designer.cs similarity index 100% rename from src/dotnet-getdocument/Properties/Resources.Designer.cs rename to src/Mvc/src/dotnet-getdocument/Properties/Resources.Designer.cs diff --git a/src/dotnet-getdocument/Resources.resx b/src/Mvc/src/dotnet-getdocument/Resources.resx similarity index 100% rename from src/dotnet-getdocument/Resources.resx rename to src/Mvc/src/dotnet-getdocument/Resources.resx diff --git a/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets b/src/Mvc/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets similarity index 100% rename from src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets rename to src/Mvc/src/dotnet-getdocument/ServiceProjectReferenceMetadata.targets diff --git a/src/dotnet-getdocument/dotnet-getdocument.csproj b/src/Mvc/src/dotnet-getdocument/dotnet-getdocument.csproj similarity index 100% rename from src/dotnet-getdocument/dotnet-getdocument.csproj rename to src/Mvc/src/dotnet-getdocument/dotnet-getdocument.csproj diff --git a/test/Directory.Build.props b/src/Mvc/test/Directory.Build.props similarity index 100% rename from test/Directory.Build.props rename to src/Mvc/test/Directory.Build.props diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Filters/FilterContextTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Filters/FilterContextTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Filters/FilterContextTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Filters/FilterContextTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Microsoft.AspNetCore.Mvc.Abstractions.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/BindingInfoTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelMetadataTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ModelStateDictionaryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ValueProviderResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ValueProviderResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ValueProviderResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/ModelBinding/ValueProviderResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Properties/AssemblyInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Properties/AssemblyInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Properties/AssemblyInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Abstractions.Test/Properties/AssemblyInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/ApiResponseTypeProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/DefaultApiDescriptionProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test/Microsoft.AspNetCore.Mvc.ApiExplorer.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtActionResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtRouteResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtRouteResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtRouteResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedAtRouteResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/AcceptedResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ActionResultOfTTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionMethodAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiConventionTypeAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionMatcherTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApiExplorer/ApiConventionResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ActionModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiBehaviorApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiConventionApplicationModelConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ApiVisibilityConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/AttributeRouteModelTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ClientErrorResultFilterConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ConsumesConstraintForFormFileParameterConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ControllerModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InferParameterBindingInfoConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/InvalidModelStateFilterConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/ParameterModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/PropertyModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationModels/RouteTokenTransformerConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/ApplicationPartManagerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/AssemblyPartTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ApplicationParts/RelatedAssemblyPartTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Authorization/AuthorizeFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestObjectResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestObjectResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestObjectResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestObjectResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BadRequestResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/BindAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BindAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/BindAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/BindAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcApplicationBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcAreaRouteBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcAreaRouteBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcAreaRouteBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Builder/MvcAreaRouteBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ChallengeResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ChallengeResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ChallengeResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ChallengeResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictObjectResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictObjectResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictObjectResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictObjectResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConflictResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ConsumesAttributeTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ContentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ControllerBaseTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerActivatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerActivatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerActivatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerActivatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFactoryProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFactoryProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFactoryProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFactoryProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ControllerFeatureProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/DefaultControllerFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ServiceBasedControllerActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ServiceBasedControllerActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ServiceBasedControllerActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Controllers/ServiceBasedControllerActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtActionResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtActionResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtActionResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtActionResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtRouteResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtRouteResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtRouteResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedAtRouteResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/CreatedResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/ApplicationModelConventionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/ApplicationModelConventionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/ApplicationModelConventionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/ApplicationModelConventionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/DependencyInjection/MvcCoreServiceCollectionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/EmptyResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/EmptyResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/EmptyResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/EmptyResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileContentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FileStreamResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ActionFilterAttributeTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/FilterCollectionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/FilterCollectionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/FilterCollectionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/FilterCollectionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/MiddlewareFilterAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ResultFilterAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ResultFilterAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ResultFilterAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Filters/ResultFilterAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/FlushReportingStream.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FlushReportingStream.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/FlushReportingStream.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/FlushReportingStream.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ForbidResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterCollectionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/FormatterMappingsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/InputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/MediaTypeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/NoContentFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/OutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StreamOutputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/StringOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Formatters/TextOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundObjectResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundObjectResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundObjectResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundObjectResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpNotFoundResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkObjectResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkObjectResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkObjectResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkObjectResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpOkResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpStatusCodeResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpUnauthorizedResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpUnauthorizedResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/HttpUnauthorizedResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/HttpUnauthorizedResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ClientErrorResultFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/CompatibilitySwitchTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ConfigureCompatibilityOptionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ConfigureCompatibilityOptionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ConfigureCompatibilityOptionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ConfigureCompatibilityOptionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultActionDescriptorCollectionProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultOutputFormatterSelectorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultOutputFormatterSelectorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultOutputFormatterSelectorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/DefaultOutputFormatterSelectorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ModelStateInvalidFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ModelStateInvalidFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ModelStateInvalidFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ModelStateInvalidFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcOptionsConfigureCompatibilityOptionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcRouteHandlerTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcRouteHandlerTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcRouteHandlerTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/MvcRouteHandlerTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/NullableCompatibilitySwitchTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ObjectResultExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Infrastructure/ProblemDetalsClientErrorFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AcceptHeaderParserTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionConstraintCacheTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionMethodExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionResultTypeMapperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionResultTypeMapperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionResultTypeMapperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionResultTypeMapperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ActionSelectorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ApiBehaviorOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRouteTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AttributeRoutingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/AuthorizationApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ClientValidatorCacheTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ClientValidatorCacheTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ClientValidatorCacheTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ClientValidatorCacheTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorBuilderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorBuilderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorBuilderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorBuilderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionDescriptorProviderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerCacheTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerBinderDelegateProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultBindingMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultCollectionValidationStrategyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultComplexObjectValidationStrategyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultComplexObjectValidationStrategyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultComplexObjectValidationStrategyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultComplexObjectValidationStrategyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DefaultObjectValidatorTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/DisableRequestSizeLimitFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ElementalValueProviderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ElementalValueProviderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ElementalValueProviderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ElementalValueProviderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ExplicitIndexCollectionValidationStrategyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/FilterProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/HttpMethodActionConstraintTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/HttpMethodActionConstraintTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/HttpMethodActionConstraintTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/HttpMethodActionConstraintTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterBuilderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterConfigurationProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MiddlewareFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcCoreLoggerExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/MvcEndpointDataSourceTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ParameterDefaultValuesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ParameterDefaultValuesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ParameterDefaultValuesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ParameterDefaultValuesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/PrefixContainerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ReferenceEqualityComparerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ReferenceEqualityComparerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ReferenceEqualityComparerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ReferenceEqualityComparerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestFormLimitsFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RequestSizeLimitFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseCacheFilterExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseCacheFilterExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseCacheFilterExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseCacheFilterExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ResponseContentTypeHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/RoutePatternWriterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ShortFormDictionaryValidationStrategyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ShortFormDictionaryValidationStrategyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ShortFormDictionaryValidationStrategyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ShortFormDictionaryValidationStrategyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ValidatorCacheTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ValidatorCacheTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ValidatorCacheTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ValidatorCacheTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ViewEnginePathTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ViewEnginePathTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ViewEnginePathTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ViewEnginePathTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/LocalRedirectResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/MediaTypeCollectionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/MediaTypeCollectionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/MediaTypeCollectionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/MediaTypeCollectionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Microsoft.AspNetCore.Mvc.Core.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ArrayModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BinderTypeModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/BodyModelBinderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ByteArrayModelBinderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CancellationTokenModelBinderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/CollectionModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ComplexTypeModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DecimalModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DecimalModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DecimalModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DecimalModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DictionaryModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DoubleModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DoubleModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DoubleModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/DoubleModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/EnumTypeModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FloatingPointTypeModelBinderTestOfT.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormCollectionModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/FormFileModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/HeaderModelBinderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/KeyValuePairModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/ServicesModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Binders/SimpleTypeModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BindingSourceValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BindingSourceValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BindingSourceValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/BindingSourceValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/CompositeValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/CompositeValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/CompositeValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/CompositeValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/DefaultModelBindingContextTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/DefaultModelBindingContextTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/DefaultModelBindingContextTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/DefaultModelBindingContextTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/EnumerableValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/EnumerableValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/EnumerableValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/EnumerableValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/FormValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Internal/ValidationStackTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Internal/ValidationStackTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Internal/ValidationStackTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Internal/ValidationStackTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryFormValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/JQueryQueryStringValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceMetadataProviderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceMetadataProviderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceMetadataProviderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceMetadataProviderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/BindingSourceTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/CompositeBindingSourceTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/CompositeBindingSourceTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/CompositeBindingSourceTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/CompositeBindingSourceTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultModelMetadataTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultValidationMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultValidationMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultValidationMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DefaultValidationMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DisplayMetadataTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DisplayMetadataTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DisplayMetadataTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/DisplayMetadataTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/EmptyCompositeMetadataDetailsProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/EmptyCompositeMetadataDetailsProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/EmptyCompositeMetadataDetailsProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/EmptyCompositeMetadataDetailsProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ExcludeBindingMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ExcludeBindingMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ExcludeBindingMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ExcludeBindingMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/HasValidatorsValidationMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/MetadataDetailsProviderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/MetadataDetailsProviderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/MetadataDetailsProviderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/MetadataDetailsProviderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelAttributesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelBinderAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelBinderAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelBinderAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelBinderAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Metadata/ModelMetadataProviderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderProviderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderProviderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderProviderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBinderProviderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ModelBindingResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ParameterBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/QueryStringValueProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/RouteValueProviderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/RouteValueProviderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/RouteValueProviderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/RouteValueProviderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/StubModelBinder.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/StubModelBinder.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/StubModelBinder.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/StubModelBinder.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestModelBinderProviderContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestValueProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestValueProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestValueProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/TestValueProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/UnsupportedContentTypeFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/CompositeModelValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/CompositeModelValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/CompositeModelValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/CompositeModelValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/DefaultModelValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/ModelValidatorProviderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/ModelValidatorProviderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/ModelValidatorProviderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/Validation/ModelValidatorProviderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ValueProviderFactoryExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ValueProviderFactoryExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ValueProviderFactoryExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ModelBinding/ValueProviderFactoryExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/MvcOptionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/MvcOptionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/MvcOptionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/MvcOptionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/NonDisposableStreamTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/NonDisposableStreamTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/NonDisposableStreamTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/NonDisposableStreamTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ObjectResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/PhysicalFileResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ProducesAttributeTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Properties/AssemblyInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Properties/AssemblyInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Properties/AssemblyInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Properties/AssemblyInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToActionResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToPageResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RedirectToRouteResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RequestFormLimitsAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/RequireHttpsAttributeTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ResponseCacheAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ResponseCacheAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ResponseCacheAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ResponseCacheAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ControllerLinkGeneratorExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/EndpointRoutingUrlHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/HttpMethodProviderAttributesTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/HttpMethodProviderAttributesTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/HttpMethodProviderAttributesTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/HttpMethodProviderAttributesTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/KnownRouteValueConstraintTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/PageLinkGeneratorExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/RouteTemplateProviderAttributesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/RouteTemplateProviderAttributesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/RouteTemplateProviderAttributesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/RouteTemplateProviderAttributesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperBaseTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/UrlHelperTestBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/SerializableErrorTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SerializableErrorTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/SerializableErrorTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SerializableErrorTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ServiceFilterAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SignInResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/SignOutResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TestApplicationPart.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TestFeatureProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/TypeFilterAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityObjectResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityObjectResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityObjectResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityObjectResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/UnprocessableEntityResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/ValidationProblemDetailsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/VirtualFileResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/xunit.runner.json b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/xunit.runner.json similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.Test/xunit.runner.json rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.Test/xunit.runner.json diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ActivityReplacer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/CommonResourceInvokerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/LinkBuilder.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/MediaTypeAssert.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/Microsoft.AspNetCore.Mvc.Core.TestCommon.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/NonSeekableReadableStream.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/RoutingResult.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/SimpleValueProviderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestClientModelValidatorProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpRequestStreamReaderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestHttpResponseStreamWriterFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelBinderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelMetadataProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/TestModelValidatorProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Core.TestCommon/ValidationAttributeUtil.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/CorsAuthorizationFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/CorsAuthorizationFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/CorsAuthorizationFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/CorsAuthorizationFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/DisableCorsAuthorizationFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/DisableCorsAuthorizationFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/DisableCorsAuthorizationFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/DisableCorsAuthorizationFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsHttpMethodActionConstraintTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsHttpMethodActionConstraintTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsHttpMethodActionConstraintTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Internal/CorsHttpMethodActionConstraintTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Cors.Test/Microsoft.AspNetCore.Mvc.Cors.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/DataAnnotationsModelValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/CompareAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsClientModelValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsClientModelValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsClientModelValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsClientModelValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataAnnotationsModelValidatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataMemberRequiredBindingMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataMemberRequiredBindingMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataMemberRequiredBindingMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataMemberRequiredBindingMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataTypeClientModelValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataTypeClientModelValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataTypeClientModelValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DataTypeClientModelValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DefaultModelClientValidatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DefaultModelClientValidatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DefaultModelClientValidatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/DefaultModelClientValidatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/FileExtensionsAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MaxLengthAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MinLengthAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ModelValidationResultComparer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MvcDataAnnotationsMvcOptionsSetup.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MvcDataAnnotationsMvcOptionsSetup.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MvcDataAnnotationsMvcOptionsSetup.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/MvcDataAnnotationsMvcOptionsSetup.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/NumericClientModelValidatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RangeAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/RequiredAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/StringLengthAttributeAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestModelNameProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestModelNameProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestModelNameProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestModelNameProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/TestResources.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidatableObjectAdapterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidatableObjectAdapterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidatableObjectAdapterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidatableObjectAdapterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterOfTAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Internal/ValidationAttributeAdapterProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Properties/Resources.Designer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Properties/Resources.Designer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Properties/Resources.Designer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Properties/Resources.Designer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Resources.resx b/src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Resources.resx similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Resources.resx rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.DataAnnotations.Test/Resources.resx diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Internal/JsonResultExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonPatchOperationsArrayProviderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/JsonResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Json.Test/MvcJsonOptionsExtensionsTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/FlushReportingStream.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/FlushReportingStream.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/FlushReportingStream.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/FlushReportingStream.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumerableTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumerableTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumerableTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumerableTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumeratorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumeratorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumeratorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/DelegatingEnumeratorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/EnumerableWrapperProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapper.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapper.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapper.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapper.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProviderFactory.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProviderFactory.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProviderFactory.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/PersonWrapperProviderFactory.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableErrorWrapperTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Internal/SerializableWrapperProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/Person.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/Person.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/Person.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/Person.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/PersonList.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/PersonList.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/PersonList.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/Models/PersonList.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetails21WrapperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperProviderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ProblemDetailsWrapperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetails21WrapperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/ValidationProblemDetailsWrapperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssert.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssert.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssert.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssert.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssertTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssertTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssertTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlAssertTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerMvcOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlDataContractSerializerOutputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerMvcOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Formatters.Xml.Test/XmlSerializerOutputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryAuthTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTestHelper.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AntiforgeryTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiBehaviorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApiExplorerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ApplicationModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AsyncActionsTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AsyncActionsTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/AsyncActionsTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/AsyncActionsTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicApiTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/BasicViewsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CompilationOptionsTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CompilationOptionsTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/CompilationOptionsTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CompilationOptionsTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeEndpointRoutingTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ConsumesAttributeTestsBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ContentNegotiationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ControllerFromServicesTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ControllerFromServicesTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ControllerFromServicesTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ControllerFromServicesTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsEndpointRoutingTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/CorsTestsBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DataAnnotationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultOrderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultOrderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultOrderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultOrderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultValuesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultValuesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultValuesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DefaultValuesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DirectivesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DirectivesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DirectivesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DirectivesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DoNotRespectBrowserAcceptHeaderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DoNotRespectBrowserAcceptHeaderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/DoNotRespectBrowserAcceptHeaderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/DoNotRespectBrowserAcceptHeaderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ErrorPageTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ExceptionInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ExceptionInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ExceptionInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ExceptionInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FileResultTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FileResultTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/FileResultTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FileResultTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FiltersTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FlushPointTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FlushPointTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/FlushPointTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FlushPointTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FormFileUploadTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FormFileUploadTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/FormFileUploadTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/FormFileUploadTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/GlobalAuthorizationFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlGenerationWithCultureTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlHelperOptionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlHelperOptionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlHelperOptionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/HtmlHelperOptionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicApiFixture.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/BasicViewsFixture.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerMiddleware.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerMiddleware.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerMiddleware.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerMiddleware.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerStartupFilter.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerStartupFilter.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerStartupFilter.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/CultureReplacerStartupFilter.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/HttpClientExtensions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/IHtmlDocumentExtensions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcEncodedTestFixtureOfT.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcEncodedTestFixtureOfT.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcEncodedTestFixtureOfT.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcEncodedTestFixtureOfT.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcTestFixture.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcWebApplicationBuilderExtensions.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcWebApplicationBuilderExtensions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcWebApplicationBuilderExtensions.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/MvcWebApplicationBuilderExtensions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/ResourceFile.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/TestCulture.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/TestCulture.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/TestCulture.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Infrastructure/TestCulture.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputObjectValidationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/InputValidationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonPatchInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonPatchInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonPatchInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonPatchInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/JsonResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGenerationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGenerationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGenerationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGenerationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/LinkGeneratorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/Microsoft.AspNetCore.Mvc.FunctionalTests.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcSandboxTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcSandboxTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcSandboxTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/MvcSandboxTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/OutputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/OutputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/OutputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/OutputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorBuildTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorBuildTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorBuildTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorBuildTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorFileUpdateTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPageModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesNamespaceTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesNamespaceTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesNamespaceTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesNamespaceTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesViewSearchTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesViewSearchTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesViewSearchTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesViewSearchTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesWithBasePathTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorViewLocationSpecificationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RemoteAttributeValidationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestFormLimitsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestFormLimitsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestFormLimitsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestFormLimitsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesEndpointRoutingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestServicesTestBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RequestSizeLimitTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RespectBrowserAcceptHeaderTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RespectBrowserAcceptHeaderTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RespectBrowserAcceptHeaderTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RespectBrowserAcceptHeaderTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingEndpointRoutingWithoutRazorPagesTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingTestsBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RoutingWithoutRazorPagesTestsBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SerializableErrorTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SerializableErrorTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/SerializableErrorTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SerializableErrorTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/SimpleTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/StreamOutputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/StreamOutputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/StreamOutputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/StreamOutputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelperComponentTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelperComponentTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelperComponentTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelperComponentTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersFromServicesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersFromServicesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersFromServicesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersFromServicesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TagHelpersTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInCookiesUsingCookieConsentTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInSessionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInSessionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInSessionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataInSessionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataPropertyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataPropertyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataPropertyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataPropertyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TempDataTestBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureInheritanceTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureInheritanceTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureInheritanceTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureInheritanceTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/TestingInfrastructureTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/UrlResolutionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/UrlResolutionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/UrlResolutionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/UrlResolutionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningEndpointRoutingTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/VersioningTestsBase.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewComponentFromServicesTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/ViewEngineTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimActionSelectionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimBasicTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimParameterBindingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimParameterBindingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimParameterBindingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/WebApiCompatShimParameterBindingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerFormattersWrappingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlDataContractSerializerInputFormatterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerFormattersWrappingTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/XmlSerializerInputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Area1.RemoteAttribute_Home.Create.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ActionLinkView.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.CSharp7View.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.CSharp7View.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.CSharp7View.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.CSharp7View.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.PlainView.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ViewWithPrefixedAttributeValue.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ViewWithPrefixedAttributeValue.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ViewWithPrefixedAttributeValue.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Home.ViewWithPrefixedAttributeValue.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.PassThrough.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.PassThrough.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.PassThrough.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.PassThrough.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/BasicWebSite.Root.RemoteAttribute_Home.Create.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert1.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert2.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/CacheTagHelper_CanCachePortionsOfViewsPartialViewsAndViewComponents.Assert3.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.AtViewModel.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.AtViewModel.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.AtViewModel.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.AtViewModel.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.NullViewModel.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.NullViewModel.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.NullViewModel.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.NullViewModel.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.ViewModel.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.ViewModel.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.ViewModel.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.CheckViewData.ViewModel.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Customer.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.AttributesWithBooleanValues.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.CreateWarehouse.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.CreateWarehouse.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.CreateWarehouse.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.CreateWarehouse.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Customer.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Customer.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Customer.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Customer.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EditWarehouse.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EmployeeList.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EmployeeList.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EmployeeList.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.EmployeeList.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Environment.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Environment.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Environment.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Environment.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Form.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Form.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Form.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Form.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Image.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Image.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Image.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Image.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Index21Compat.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Input.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Input.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Input.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Input.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Link.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Order.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.OrderUsingHtmlHelpers.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.PartialTagHelperWithoutModel.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.PartialTagHelperWithoutModel.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.PartialTagHelperWithoutModel.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.PartialTagHelperWithoutModel.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Product.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductList.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductList.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductList.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductList.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpers.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpers.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpers.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpers.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpersWithNullModel.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpersWithNullModel.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpersWithNullModel.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.ProductListUsingTagHelpersWithNullModel.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Script.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Warehouse.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Warehouse.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Warehouse.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/HtmlGenerationWebSite.HtmlGeneration_Home.Warehouse.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Details.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Details.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Details.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Details.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.Invalid.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.Invalid.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.Invalid.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.Invalid.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ModelBindingWebSite.Vehicle.Edit.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorPagesWebSite.SimpleForms.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.AddTagHelperComponent.AddComponent.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.AddTagHelperComponent.AddComponent.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.AddTagHelperComponent.AddComponent.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.AddTagHelperComponent.AddComponent.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Body.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Head.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Head.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Head.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.TagHelperComponent.Head.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/RazorWebSite.UrlResolution.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.Invalid.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Create.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.Details.AfterCreate.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.DuplicateAntiforgeryTokenRegistration.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.DuplicateAntiforgeryTokenRegistration.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.DuplicateAntiforgeryTokenRegistration.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Employee.DuplicateAntiforgeryTokenRegistration.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.CustomEncoder.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.CustomEncoder.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.CustomEncoder.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.CustomEncoder.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.NullEncoder.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.NullEncoder.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.NullEncoder.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.NullEncoder.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.ThreeEncoders.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.ThreeEncoders.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.ThreeEncoders.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.ThreeEncoders.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.TwoEncoders.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.TwoEncoders.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.TwoEncoders.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Encoders.TwoEncoders.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.About.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Help.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.Index.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.Encoded.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.Encoded.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.Encoded.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.Encoded.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.UnboundDynamicAttributes.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/TagHelpersWebSite.Home.ViewComponentTagHelpers.html diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesPropertyErrorsInViews.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_PopulatesValidationSummary.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/UpdateDealerVehicle_UpdateSuccessful.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ViewEngineController.ViewWithPaths.txt b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ViewEngineController.ViewWithPaths.txt similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ViewEngineController.ViewWithPaths.txt rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/compiler/resources/ViewEngineController.ViewWithPaths.txt diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json b/src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.FunctionalTests/xunit.runner.json diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ActionParametersIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ArrayModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/AuthorizeFilterIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindPropertyIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindPropertyIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindPropertyIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindPropertyIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BinderTypeBasedModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BinderTypeBasedModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/BinderTypeBasedModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BinderTypeBasedModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindingSourceMetadataProviderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindingSourceMetadataProviderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindingSourceMetadataProviderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BindingSourceMetadataProviderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/BodyValidationIntegrationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ByteArrayModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CancellationTokenModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CancellationTokenModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/CancellationTokenModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CancellationTokenModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CollectionModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CompanyNameAttribute.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CompanyNameAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/CompanyNameAttribute.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/CompanyNameAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ComplexTypeModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/DictionaryModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ExcludeBindingMetadataProviderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ExcludeBindingMetadataProviderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ExcludeBindingMetadataProviderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ExcludeBindingMetadataProviderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormCollectionModelBindingIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/FormFileModelBindingIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/GenericModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HasValidatorsValidationMetadataProviderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/HeaderModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/JQueryFormatModelBindingIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/JQueryFormatModelBindingIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/JQueryFormatModelBindingIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/JQueryFormatModelBindingIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/KeyValuePairModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Microsoft.AspNetCore.Mvc.IntegrationTests.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelBindingTestHelper.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelPrefixSelectionIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelPrefixSelectionIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelPrefixSelectionIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ModelPrefixSelectionIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Product.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Product.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Product.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Product.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductDetails.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductDetails.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductDetails.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductDetails.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductViewModel.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductViewModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductViewModel.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/ProductViewModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Software.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Software.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Software.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/Software.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/SoftwareViewModel.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/SoftwareViewModel.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/SoftwareViewModel.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/Models/SoftwareViewModel.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ParameterBinderExtensions.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ParameterBinderExtensions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ParameterBinderExtensions.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ParameterBinderExtensions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ProductValidatorAttribute.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ProductValidatorAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ProductValidatorAttribute.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ProductValidatorAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ServicesModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ServicesModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ServicesModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ServicesModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/SimpleTypeModelBinderIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TestMvcOptions.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryUpdateModelIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/TryValidateModelIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.IntegrationTests/ValidationIntegrationTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerOfTTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/HtmlLocalizerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/Microsoft.AspNetCore.Mvc.Localization.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationMvcCoreBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/MvcLocalizationServiceCollectionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/TestStringLocalizer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/TestStringLocalizer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/TestStringLocalizer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/TestStringLocalizer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Localization.Test/ViewLocalizerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/ViewLocalizerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Localization.Test/ViewLocalizerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Localization.Test/ViewLocalizerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/ApplicationParts/RazorCompiledItemFeatureProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/ApplicationParts/RazorCompiledItemFeatureProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/ApplicationParts/RazorCompiledItemFeatureProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/ApplicationParts/RazorCompiledItemFeatureProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/MetadataReferenceFeatureProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Compilation/ViewsFeatureProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/DependencyInjection/MvcRazorMvcCoreBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CSharpCompilerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ChecksumValidatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/CompilerFailedExceptionFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorPageFactoryProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorReferenceManagerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultRazorViewEngineFileProviderAccessorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/DefaultTagHelperFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ExpressionRewriterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ExpressionRewriterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ExpressionRewriterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ExpressionRewriterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectFileSystemTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectFileSystemTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectFileSystemTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/FileProviderRazorProjectFileSystemTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorPagePropertyActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewCompilerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/RazorViewEngineOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ViewPathTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ViewPathTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ViewPathTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Internal/ViewPathTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/LanguageViewLocationExpanderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/LanguageViewLocationExpanderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/LanguageViewLocationExpanderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/LanguageViewLocationExpanderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Microsoft.AspNetCore.Mvc.Razor.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/Properties/AssemblyInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Properties/AssemblyInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/Properties/AssemblyInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/Properties/AssemblyInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateModelExpressionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageCreateTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorPageTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineOptionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewEngineTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/RazorViewTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentPropertyActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/TagHelperComponentTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TagHelpers/UrlResolutionTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/TestApplicationPart.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TestApplicationPart.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/TestApplicationPart.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/TestApplicationPart.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Razor.Test/xunit.runner.json b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/xunit.runner.json similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Razor.Test/xunit.runner.json rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Razor.Test/xunit.runner.json diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/DefaultPageApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageRouteTransformerConventionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/AutoValidateAntiforgeryPageApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageActivatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageFactoryProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelActivatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DefaultPageModelFactoryProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/DisallowOptionsRequestsPageFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageDirectiveFeatureTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageViewLocationExpanderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageViewLocationExpanderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageViewLocationExpanderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageViewLocationExpanderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/ServiceBasedPageModelActivatorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/AuthorizationPageApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageActionDescriptorBuilderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageHandlerMethodSelectorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ExecutorFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageBinderFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerPageFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerPageFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerPageFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerPageFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageHandlerResultFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageSaveTempDataPropertyFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageViewDataAttributeFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesRazorViewEngineOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ResponseCacheFilterApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/TempDataFilterPageApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ViewDataAttributePageApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ViewDataAttributePageApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ViewDataAttributePageApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/ViewDataAttributePageApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Microsoft.AspNetCore.Mvc.RazorPages.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/PageTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/TestRazorProjectFileSystem.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/TestRazorProjectFileSystem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/TestRazorProjectFileSystem.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/TestRazorProjectFileSystem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/AnchorTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/CacheTagKeyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DefaultFileVersionProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DistributedCacheTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DistributedCacheTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DistributedCacheTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/DistributedCacheTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/EnvironmentTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/EnvironmentTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/EnvironmentTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/EnvironmentTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormActionTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/FormTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ImageTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/InputTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/AttributeMatcherTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/DefaultTagHelperActivatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/GlobbingUrlBuilderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/JavaScriptResourcesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/JavaScriptResourcesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/JavaScriptResourcesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Internal/JavaScriptResourcesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LabelTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/LinkTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/Microsoft.AspNetCore.Mvc.TagHelpers.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/OptionTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/OptionTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/OptionTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/OptionTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/PartialTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/RenderAtEndOfFormTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/RenderAtEndOfFormTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/RenderAtEndOfFormTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/RenderAtEndOfFormTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ScriptTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/SelectTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperLogger.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperLogger.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperLogger.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperLogger.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TagHelperOutputExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TestableHtmlGenerator.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/TextAreaTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationMessageTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TagHelpers.Test/ValidationSummaryTagHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/ApplicationParts/ApplicationAssembliesProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/IntegrationTest/CompatibilitySwitchIntegrationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/JsonPatchExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/JsonPatchExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/JsonPatchExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/JsonPatchExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/Microsoft.AspNetCore.Mvc.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/MvcOptionsSetupTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Test/xunit.runner.json diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionDescriptor.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionDescriptor.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionDescriptor.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionDescriptor.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionResult.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionResult.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionResult.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyActionResult.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyHttpContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyHttpContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyHttpContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyHttpContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyPage.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyPage.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyPage.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyPage.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyRouteData.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyRouteData.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyRouteData.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyRouteData.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyView.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyView.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyView.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyView.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentResult.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentResult.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentResult.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewComponentResult.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewContext.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewContext.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewContext.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/IProxyViewContext.cs diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/Microsoft.AspNetCore.Mvc.TestDiagnosticListener.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/TestDiagnosticListener.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/TestDiagnosticListener.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/TestDiagnosticListener.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.TestDiagnosticListener/TestDiagnosticListener.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ControllerUnitTestabilityTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/DependencyInjection/MvcViewFeaturesMvcCoreBuilderExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/AutoValidateAntiforgeryTokenAuthorizationFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/AutoValidateAntiforgeryTokenAuthorizationFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/AutoValidateAntiforgeryTokenAuthorizationFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/AutoValidateAntiforgeryTokenAuthorizationFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerSaveTempDataPropertyFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ControllerViewDataAttributeFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultDisplayTemplatesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/DefaultEditorTemplatesTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ExpressionMetadataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/FormatWeekHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/FormatWeekHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/FormatWeekHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/FormatWeekHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/HtmlAttributePropertyHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/HtmlAttributePropertyHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/HtmlAttributePropertyHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/HtmlAttributePropertyHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/LifecyclePropertyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/LifecyclePropertyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/LifecyclePropertyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/LifecyclePropertyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyComparerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/MemberExpressionCacheKeyTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedBufferedTextWriterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedBufferedTextWriterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedBufferedTextWriterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedBufferedTextWriterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/PagedCharBufferTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/SaveTempDataFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataSerializerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataSerializerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataSerializerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TempDataSerializerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TemplateRendererTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TemplateRendererTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TemplateRendererTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/TemplateRendererTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ValidateAntiforgeryTokenAuthorizationFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTextWriterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTextWriterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTextWriterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewBufferTextWriterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributeApplicationModelProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributeApplicationModelProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributeApplicationModelProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributeApplicationModelProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Internal/ViewDataAttributePropertyProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Constants.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Constants.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Constants.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Constants.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Model.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Model.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Model.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Model/Model.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ModelStateDictionaryExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ModelStateDictionaryExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ModelStateDictionaryExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ModelStateDictionaryExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/PartialViewResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/PartialViewResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/PartialViewResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/PartialViewResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/AssemblyInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/AssemblyInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/AssemblyInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/AssemblyInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/Resources.Designer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/Resources.Designer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/Resources.Designer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Properties/Resources.Designer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/RemoteAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/DefaultTemplatesUtilities.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperCheckboxTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayNameExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayTextTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayTextTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayTextTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDisplayTextTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperDropDownListExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperEditorExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperFormTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperHiddenTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLabelExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperLinkGenerationTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperListBoxExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperNameExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperNameExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperNameExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperNameExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPartialExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperPasswordTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperRadioButtonExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperSelectTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextAreaTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperTextBoxTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationMessageExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValidationSummaryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlHelperValueTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlStringTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlStringTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlStringTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/HtmlStringTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TagBuilderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/TestResources.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Rendering/ViewContextTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Resources.resx b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Resources.resx similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Resources.resx rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/Resources.resx diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/SkipStatusCodePagesAttributeTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/SkipStatusCodePagesAttributeTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/SkipStatusCodePagesAttributeTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/SkipStatusCodePagesAttributeTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/TestApplicationPart.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponentTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ContentViewComponentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentActivatorTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentDescriptorProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/DefaultViewComponentSelectorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/HtmlContentViewComponentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentContextTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentContextTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentContextTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentContextTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentConventionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentConventionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentConventionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentConventionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewComponentFeatureProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewComponents/ViewViewComponentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewEngines/CompositeViewEngineTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AntiforgeryExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/AttributeDictionaryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CachedExpressionCompilerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CookieTempDataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CookieTempDataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CookieTempDataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/CookieTempDataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultHtmlGeneratorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultValidationHtmlAttributeProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultValidationHtmlAttributeProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultValidationHtmlAttributeProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/DefaultValidationHtmlAttributeProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/JsonHelperTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ModelExplorerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/PartialViewResultExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/SessionStateTempDataProviderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/SessionStateTempDataProviderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/SessionStateTempDataProviderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/SessionStateTempDataProviderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/StringHtmlContentTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/StringHtmlContentTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/StringHtmlContentTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/StringHtmlContentTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryFactoryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryFactoryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryFactoryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryFactoryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/TempDataDictionaryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryOfTModelTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataDictionaryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataOfTTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataOfTTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataOfTTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewDataOfTTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewFeatures/ViewResultExecutorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.ViewFeatures.Test/ViewResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlContentUtilities.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/HtmlGeneratorUtilities.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/Microsoft.AspNetCore.Mvc.Views.TestCommon.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryContent.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestDirectoryFileInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileChangeToken.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileInfo.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestFileProvider.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorCompiledItem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestRazorProjectItem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/TestViewBufferScope.cs diff --git a/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.Views.TestCommon/VirtualRazorProjectFileSystem.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerActionDiscoveryTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ApiControllerTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/BadRequestErrorMessageResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/BadRequestErrorMessageResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/BadRequestErrorMessageResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/BadRequestErrorMessageResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ConflictResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ConflictResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ConflictResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ConflictResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/DefaultContentNegotiatorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/DefaultContentNegotiatorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/DefaultContentNegotiatorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/DefaultContentNegotiatorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ExceptionResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ExceptionResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ExceptionResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/ExceptionResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/FormDataCollectionExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/FormDataCollectionExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/FormDataCollectionExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/FormDataCollectionExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpErrorTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpErrorTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpErrorTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpErrorTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageExtensionsTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageExtensionsTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageExtensionsTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageExtensionsTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageFeatureTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageModelBinderTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageModelBinderTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageModelBinderTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpRequestMessage/HttpRequestMessageModelBinderTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionActionFilterTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseExceptionTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/HttpResponseMessageOutputFormatterTests.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InternalServerErrorResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InternalServerErrorResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InternalServerErrorResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InternalServerErrorResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InvalidModelStateResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InvalidModelStateResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InvalidModelStateResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/InvalidModelStateResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest.csproj diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockContentNegotiator.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockContentNegotiator.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockContentNegotiator.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockContentNegotiator.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockMediaTypeFormatter.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockMediaTypeFormatter.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockMediaTypeFormatter.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/Mocks/MockMediaTypeFormatter.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/NegotiatedContentResultTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/NegotiatedContentResultTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/NegotiatedContentResultTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/NegotiatedContentResultTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/OverloadActionConstraintTest.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/FlagsEnum.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/FlagsEnum.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/FlagsEnum.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/FlagsEnum.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/LongEnum.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/LongEnum.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/LongEnum.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/LongEnum.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeAssert.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeAssert.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeAssert.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeAssert.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeConstants.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeHeaderValueComparer.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeHeaderValueComparer.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeHeaderValueComparer.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/MediaTypeHeaderValueComparer.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/RefTypeTestData.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/RefTypeTestData.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/RefTypeTestData.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/RefTypeTestData.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/SimpleEnum.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/SimpleEnum.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/SimpleEnum.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/SimpleEnum.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestData.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestData.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestData.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestData.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataHolder.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataHolder.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataHolder.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataHolder.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataSetAttribute.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataVariations.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataVariations.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataVariations.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TestDataVariations.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TypeAssert.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TypeAssert.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TypeAssert.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/TypeAssert.cs diff --git a/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/ValueTypeTestData.cs b/src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/ValueTypeTestData.cs similarity index 100% rename from test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/ValueTypeTestData.cs rename to src/Mvc/test/Microsoft.AspNetCore.Mvc.WebApiCompatShimTest/TestUtils/ValueTypeTestData.cs diff --git a/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs b/src/Mvc/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs similarity index 100% rename from test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs rename to src/Mvc/test/Mvc.Analyzers.Test/AttributesShouldNotBeAppliedToPageModelAnalyzerTest.cs diff --git a/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs b/src/Mvc/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs similarity index 100% rename from test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs rename to src/Mvc/test/Mvc.Analyzers.Test/AvoidHtmlPartialAnalyzerTest.cs diff --git a/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs b/src/Mvc/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs similarity index 100% rename from test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs rename to src/Mvc/test/Mvc.Analyzers.Test/CodeAnalysisExtensionsTest.cs diff --git a/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs b/src/Mvc/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs similarity index 100% rename from test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs rename to src/Mvc/test/Mvc.Analyzers.Test/Infrastructure/MvcDiagnosticAnalyzerRunner.cs diff --git a/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs b/src/Mvc/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs similarity index 100% rename from test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs rename to src/Mvc/test/Mvc.Analyzers.Test/Infrastructure/MvcTestSource.cs diff --git a/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj b/src/Mvc/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj similarity index 100% rename from test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj rename to src/Mvc/test/Mvc.Analyzers.Test/Mvc.Analyzers.Test.csproj diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageHandlerMethod.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAttributeIsAppliedToBaseType.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageHandlerMethod.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethod.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodDerivingFromCustomModel.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfFiltersAreAppliedToPageHandlerMethodForTypeWithPageModelAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttribute_IsAppliedToPageModel.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/DiagnosticsAreReturned_IfRouteAttributesAreAppliedToPageHandlerMethod.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerActions.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForControllerBaseActions.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForNonHandlerMethodsWithAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_ForPageHandlersWithNonFilterAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAllowAnonymousIsAppliedToPageModel.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfAuthorizeAttributeIsAppliedToPageModel.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AttributesShouldNotBeAppliedToPageModelAnalyzerTest/NoDiagnosticsAreReturned_IfFiltersAreAppliedToPageModel.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_InSections.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfHtmlPartial_WithAdditionalParameters.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_InSections.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/DiagnosticsAreReturned_ForUseOfRenderPartial_WithAdditionalParameters.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForNonUseOfHtmlPartial.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfHtmlPartialAsync.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/AvoidHtmlPartialAnalyzerTest/NoDiagnosticsAreReturned_ForUseOfRenderPartialAsync.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_BaseTypeWithAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnMethodWithoutAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_OnTypeWithoutAttributes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithMethodOverridding.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithNewMethod.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/GetAttributes_WithoutMethodOverriding.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsFalseIfSymbolDoesNotHaveAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnMethods.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverriddenMethods.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnOverridenProperties.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForAttributesOnProperties.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueForInterfaceContractOnAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfBaseTypeHasAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/HasAttribute_ReturnsTrueIfTypeHasAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsFalseForDifferentTypes.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfAncestorTypeImplementsInterface.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeImplementsInterface.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypeIsBaseClass.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/CodeAnalysisExtensionsTest/IsAssignable_ReturnsTrueIfTypesAreExact.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForControllerActionsWithParametersThatMatchProperties.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_ForModelBoundParameters.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/DiagnosticsAreReturned_IfModelNameProviderIsUsedToModifyParameterName.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/GetNameTests.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresFields.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresMethods.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresNonPublicProperties.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_IgnoresStaticProperties.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForFromBodyParameter.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_ForParametersWithCustomModelBinder.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameParameter.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsFalse_IfBindingSourceAttributeIsUsedToRenameProperty.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfModelBinderAttributeIsUsedToRenameParameter.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameIsTheSameAsModelProperty.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfParameterNameWithBinderAttributeIsTheSameNameAsModelProperty.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/IsProblematicParameter_ReturnsTrue_IfPropertyWithModelBindingAttributeHasSameNameAsParameter.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForApiControllers.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedForNonActions.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/NoDiagnosticsAreReturnedIfParameterIsRenamedUsingBindingAttribute.cs diff --git a/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs b/src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TestFiles/TopLevelParameterNameAnalyzerTest/SpecifiesModelTypeTests.cs diff --git a/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs b/src/Mvc/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs similarity index 100% rename from test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs rename to src/Mvc/test/Mvc.Analyzers.Test/TopLevelParameterNameAnalyzerTest.cs diff --git a/test/Mvc.Analyzers.Test/xunit.runner.json b/src/Mvc/test/Mvc.Analyzers.Test/xunit.runner.json similarity index 100% rename from test/Mvc.Analyzers.Test/xunit.runner.json rename to src/Mvc/test/Mvc.Analyzers.Test/xunit.runner.json diff --git a/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/ActualApiResponseMetadataFactoryTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/AddResponseTypeAttributeCodeFixProviderIntegrationTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/ApiControllerFactsTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/ApiConventionAnalyzerIntegrationTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/DeclaredApiResponseMetadataTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj b/src/Mvc/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj similarity index 100% rename from test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj rename to src/Mvc/test/Mvc.Api.Analyzers.Test/Mvc.Api.Analyzers.Test.csproj diff --git a/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/MvcFactsTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/SymbolApiConventionMatcherTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/SymbolApiResponseMetadataProviderTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/GetDefaultStatusCodeTest.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/InspectReturnExpressionTests.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ActualApiResponseMetadataFactoryTest/TryGetActualResponseMetadataTests.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsFullyQualifiedProducesResponseType.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodes.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsMissingStatusCodesAndTypes.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsNumericLiteralForNonExistingStatusCodeConstants.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsResponseTypeWhenDifferentFromErrorType.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodes.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromConstructorParameters.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromMethodParameters.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsStatusCodesFromObjectInitializer.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixAddsSuccessStatusCode.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionAddsMissingStatusCodes.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/AddResponseTypeAttributeCodeFixProviderIntegrationTest/CodeFixWithConventionMethodAddsMissingStatusCodes.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecks.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksUsingEquality.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/DiagnosticsAreReturned_ForApiActionsWithModelStateChecksWithoutBracing.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsCheckingAdditionalConditions.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturning400FromNonModelStateIsValidBlocks.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsReturningNot400FromNonModelStateIsValidBlock.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiActionsWithoutModelStateChecks.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesIfBlockWithoutBraces.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithEqualityCheck.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Input.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiActionsDoNotRequireExplicitModelValidationCheckCodeFixProviderTest/CodeFixRemovesModelStateIsInvalidBlockWithIfNotCheck.Output.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/IsApiControllerAction_ReturnsTrue_IfAttributeIsDeclaredOnAssembly.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiControllerFactsTest/TestFile.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutAnyAttributes.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForActionResultOfTReturningMethodWithoutSomeAttributes.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_ForControllerWithCustomConvention.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodReturningValueTaskWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfAsyncMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithApiConventionMethod_ReturnsUndocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeAsynchronouslyReturnsValue_WithoutDocumentation.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttributeReturnsValue_WithoutDocumentation.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithAttribute_ReturnsDerivedType.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_DoesNotReturnDocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithConvention_ReturnsUndocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotDocumentSuccessStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_DoesNotReturnDocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/DiagnosticsAreReturned_IfMethodWithProducesResponseTypeAttribute_ReturnsUndocumentedStatusCode.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_IfStatusCodesCannotBeInferred.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForApiController_WithAllDocumentedStatusCodes.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForNonApiController.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForOkResultReturningAction.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForRazorPageModels.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLambdas.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/ApiConventionAnalyzerIntegrationTest/NoDiagnosticsAreReturned_ForReturnStatementsInLocalFunctions.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerActionTests.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/MvcFactsTest/IsControllerTests.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiConventionMatcherTest/SymbolApiConventionMatcherTestFile.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsProblemDetails_IfNoAttributeIsDiscovered.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAction.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtAssembly.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetErrorResponseType_ReturnsTypeDefinedAtController.cs diff --git a/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs b/src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs similarity index 100% rename from test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs rename to src/Mvc/test/Mvc.Api.Analyzers.Test/TestFiles/SymbolApiResponseMetadataProviderTest/GetResponseMetadataTests.cs diff --git a/test/MvcTests.ruleset b/src/Mvc/test/MvcTests.ruleset similarity index 100% rename from test/MvcTests.ruleset rename to src/Mvc/test/MvcTests.ruleset diff --git a/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ActionDescriptorChangeProvider.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerDataFilter.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerInboundOutboundConvention.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerInboundOutboundConvention.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerInboundOutboundConvention.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerInboundOutboundConvention.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerRouteChangeConvention.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityDisabledConvention.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerVisibilityEnabledConvention.cs diff --git a/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.csproj b/src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.csproj similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.csproj rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ApiExplorerWebSite.csproj diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerApiController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerHttpMethodController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerInboundOutboundController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerInboundOutboundController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerInboundOutboundController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerInboundOutboundController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetByConventionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerNameSetExplicitlyController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerParametersController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerReloadableController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseContentTypeOverrideOnActionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeOverrideOnActionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithApiConventionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithAttributeController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerResponseTypeWithoutAttributeController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerRouteAndPathParametersInformationController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityDisabledByConventionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilityEnabledByConventionController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Controllers/ApiExplorerVisibilitySetExplicitlyController.cs diff --git a/test/WebSites/ApiExplorerWebSite/Models/Customer.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Models/Customer.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Models/Customer.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Models/Customer.cs diff --git a/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Models/CustomerCommentsDTO.cs diff --git a/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Models/OrderDTO.cs diff --git a/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Models/OrderDetailsDTO.cs diff --git a/test/WebSites/ApiExplorerWebSite/Models/Product.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Models/Product.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Models/Product.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Models/Product.cs diff --git a/test/WebSites/ApiExplorerWebSite/PassThruAttribute.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/PassThruAttribute.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/PassThruAttribute.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/PassThruAttribute.cs diff --git a/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/ReloadAttribute.cs diff --git a/test/WebSites/ApiExplorerWebSite/Startup.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/Startup.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/Startup.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/Startup.cs diff --git a/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs b/src/Mvc/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs similarity index 100% rename from test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs rename to src/Mvc/test/WebSites/ApiExplorerWebSite/WellKnownChangeToken.cs diff --git a/test/WebSites/ApiExplorerWebSite/readme.md b/src/Mvc/test/WebSites/ApiExplorerWebSite/readme.md similarity index 100% rename from test/WebSites/ApiExplorerWebSite/readme.md rename to src/Mvc/test/WebSites/ApiExplorerWebSite/readme.md diff --git a/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj b/src/Mvc/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj similarity index 100% rename from test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj rename to src/Mvc/test/WebSites/ApplicationModelWebSite/ApplicationModelWebSite.csproj diff --git a/test/WebSites/ApplicationModelWebSite/Areas/Manage/Views/MultipleAreas/Index.cshtml b/src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Manage/Views/MultipleAreas/Index.cshtml similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Areas/Manage/Views/MultipleAreas/Index.cshtml rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Manage/Views/MultipleAreas/Index.cshtml diff --git a/test/WebSites/ApplicationModelWebSite/Areas/Products/Views/MultipleAreas/Index.cshtml b/src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Products/Views/MultipleAreas/Index.cshtml similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Areas/Products/Views/MultipleAreas/Index.cshtml rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Products/Views/MultipleAreas/Index.cshtml diff --git a/test/WebSites/ApplicationModelWebSite/Areas/Services/Views/MultipleAreas/Index.cshtml b/src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Services/Views/MultipleAreas/Index.cshtml similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Areas/Services/Views/MultipleAreas/Index.cshtml rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Areas/Services/Views/MultipleAreas/Index.cshtml diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ActionModelController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ActionModelController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/ActionModelController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ActionModelController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ApplicationModelController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ApplicationModelController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/ApplicationModelController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ApplicationModelController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ControllerModelController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ControllerModelController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/ControllerModelController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ControllerModelController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/LicenseController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/LicenseController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/LicenseController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/LicenseController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/MultipleAreasController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/MultipleAreasController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/MultipleAreasController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/MultipleAreasController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Controllers/ParameterModelController.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/ActionDescriptionAttribute.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ActionDescriptionAttribute.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/ActionDescriptionAttribute.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ActionDescriptionAttribute.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/ApplicationDescription.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ApplicationDescription.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/ApplicationDescription.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ApplicationDescription.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionAttribute.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionAttribute.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/CloneActionAttribute.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionAttribute.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionConvention.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionConvention.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/CloneActionConvention.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/CloneActionConvention.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/ControllerDescriptionAttribute.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ControllerDescriptionAttribute.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/ControllerDescriptionAttribute.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ControllerDescriptionAttribute.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/ControllerLicenseConvention.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ControllerLicenseConvention.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/ControllerLicenseConvention.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/ControllerLicenseConvention.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/FromHeaderConvention.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/FromHeaderConvention.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/FromHeaderConvention.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/FromHeaderConvention.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasAttribute.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasAttribute.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasAttribute.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasAttribute.cs diff --git a/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasControllerConvention.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasControllerConvention.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasControllerConvention.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Conventions/MultipleAreasControllerConvention.cs diff --git a/test/WebSites/ApplicationModelWebSite/Startup.cs b/src/Mvc/test/WebSites/ApplicationModelWebSite/Startup.cs similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Startup.cs rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Startup.cs diff --git a/test/WebSites/ApplicationModelWebSite/Views/ActionModel/Help.cshtml b/src/Mvc/test/WebSites/ApplicationModelWebSite/Views/ActionModel/Help.cshtml similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Views/ActionModel/Help.cshtml rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Views/ActionModel/Help.cshtml diff --git a/test/WebSites/ApplicationModelWebSite/Views/ActionModel/MoreHelp.cshtml b/src/Mvc/test/WebSites/ApplicationModelWebSite/Views/ActionModel/MoreHelp.cshtml similarity index 100% rename from test/WebSites/ApplicationModelWebSite/Views/ActionModel/MoreHelp.cshtml rename to src/Mvc/test/WebSites/ApplicationModelWebSite/Views/ActionModel/MoreHelp.cshtml diff --git a/test/WebSites/ApplicationModelWebSite/readme.md b/src/Mvc/test/WebSites/ApplicationModelWebSite/readme.md similarity index 100% rename from test/WebSites/ApplicationModelWebSite/readme.md rename to src/Mvc/test/WebSites/ApplicationModelWebSite/readme.md diff --git a/test/WebSites/BasicWebSite/ActionDescriptorCreationCounter.cs b/src/Mvc/test/WebSites/BasicWebSite/ActionDescriptorCreationCounter.cs similarity index 100% rename from test/WebSites/BasicWebSite/ActionDescriptorCreationCounter.cs rename to src/Mvc/test/WebSites/BasicWebSite/ActionDescriptorCreationCounter.cs diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_HomeController.cs b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_HomeController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_HomeController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_HomeController.cs diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_VerifyController.cs b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_VerifyController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_VerifyController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Controllers/RemoteAttribute_VerifyController.cs diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Create.cshtml diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/Details.cshtml diff --git a/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area1/Views/RemoteAttribute_Home/_Layout.cshtml diff --git a/test/WebSites/BasicWebSite/Areas/Area2/Controllers/RemoteAttribute_VerifyController.cs b/src/Mvc/test/WebSites/BasicWebSite/Areas/Area2/Controllers/RemoteAttribute_VerifyController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Areas/Area2/Controllers/RemoteAttribute_VerifyController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Areas/Area2/Controllers/RemoteAttribute_VerifyController.cs diff --git a/test/WebSites/BasicWebSite/BasicAuthenticationHandler.cs b/src/Mvc/test/WebSites/BasicWebSite/BasicAuthenticationHandler.cs similarity index 100% rename from test/WebSites/BasicWebSite/BasicAuthenticationHandler.cs rename to src/Mvc/test/WebSites/BasicWebSite/BasicAuthenticationHandler.cs diff --git a/test/WebSites/BasicWebSite/BasicWebSite.csproj b/src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj similarity index 100% rename from test/WebSites/BasicWebSite/BasicWebSite.csproj rename to src/Mvc/test/WebSites/BasicWebSite/BasicWebSite.csproj diff --git a/test/WebSites/BasicWebSite/Components/PassThroughViewComponent.cs b/src/Mvc/test/WebSites/BasicWebSite/Components/PassThroughViewComponent.cs similarity index 100% rename from test/WebSites/BasicWebSite/Components/PassThroughViewComponent.cs rename to src/Mvc/test/WebSites/BasicWebSite/Components/PassThroughViewComponent.cs diff --git a/test/WebSites/BasicWebSite/Components/ViewDataViewComponent.cs b/src/Mvc/test/WebSites/BasicWebSite/Components/ViewDataViewComponent.cs similarity index 100% rename from test/WebSites/BasicWebSite/Components/ViewDataViewComponent.cs rename to src/Mvc/test/WebSites/BasicWebSite/Components/ViewDataViewComponent.cs diff --git a/test/WebSites/BasicWebSite/ConfigureAuthPolicies.cs b/src/Mvc/test/WebSites/BasicWebSite/ConfigureAuthPolicies.cs similarity index 100% rename from test/WebSites/BasicWebSite/ConfigureAuthPolicies.cs rename to src/Mvc/test/WebSites/BasicWebSite/ConfigureAuthPolicies.cs diff --git a/test/WebSites/BasicWebSite/ContactsRepository.cs b/src/Mvc/test/WebSites/BasicWebSite/ContactsRepository.cs similarity index 100% rename from test/WebSites/BasicWebSite/ContactsRepository.cs rename to src/Mvc/test/WebSites/BasicWebSite/ContactsRepository.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_MediaTypeSuffix.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_MediaTypeSuffix.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_MediaTypeSuffix.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_MediaTypeSuffix.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_NoFallBackActionController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_NoFallBackActionController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_NoFallBackActionController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_NoFallBackActionController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesBaseController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesBaseController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesBaseController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesBaseController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_OveridesController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_PassThroughController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionConstraints/ConsumesAttribute_WithFallbackActionController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ActionResultOfTController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionResultOfTController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ActionResultOfTController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ActionResultOfTController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/AntiforgeryController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/AsyncActionsController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/AsyncActionsController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/AsyncActionsController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/AsyncActionsController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/AuthorizeUserController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/AuthorizeUserController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/AuthorizeUserController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/AuthorizeUserController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/BindPropertiesController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/BindPropertiesController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/BindPropertiesController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/BindPropertiesController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/BindPropertiesSupportsGetController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/BindPropertiesSupportsGetController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/BindPropertiesSupportsGetController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/BindPropertiesSupportsGetController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContactApiController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContactApiController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ContentNegotiationController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ContentNegotiationController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ContentNegotiationController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ContentNegotiationController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FallbackOnTypeBasedMatchController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FallbackOnTypeBasedMatchController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FallbackOnTypeBasedMatchController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FallbackOnTypeBasedMatchController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/FormatFilterController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/InvalidContentTypeController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentDoNotTreatNullValueAsNoContentController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentDoNotTreatNullValueAsNoContentController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentDoNotTreatNullValueAsNoContentController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoContentDoNotTreatNullValueAsNoContentController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoProducesContentOnClassController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoProducesContentOnClassController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoProducesContentOnClassController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NoProducesContentOnClassController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NormalController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NormalController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NormalController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/NormalController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentBaseController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentBaseController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentBaseController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentBaseController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentOnClassController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentOnClassController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentOnClassController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesContentOnClassController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesJsonController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesJsonController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesJsonController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesJsonController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeParametersController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeParametersController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeParametersController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeParametersController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeSuffixesController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeSuffixesController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeSuffixesController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/ProducesWithMediaTypeSuffixesController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/TextPlainController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/TextPlainController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ContentNegotiation/TextPlainController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ContentNegotiation/TextPlainController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/DefaultValuesController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/FiltersController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/FiltersController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/FiltersController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/FiltersController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/JsonResultController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/JsonResultController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/LinkGeneration/LinksController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/LinksController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/LinkGeneration/LinksController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/LinksController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/LinkGeneration/OrdersController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/OrdersController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/LinkGeneration/OrdersController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/OrdersController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/LinkGeneration/ProductsController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/ProductsController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/LinkGeneration/ProductsController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/LinkGeneration/ProductsController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/MonitorController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/MonitorController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/MonitorController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/MonitorController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/OrderController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/OrderController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/OrderController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/OrderController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/PageRouteController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/PageRouteController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/PassThroughController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/PassThroughController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/PassThroughController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/PassThroughController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_HomeController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RemoteAttribute_VerifyController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RequestFormLimitsController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestFormLimitsController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RequestFormLimitsController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestFormLimitsController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestScopedServiceController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RequestSizeLimitController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/RoutingController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/RoutingController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/RoutingController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/RoutingController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/SqlDataController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/SqlDataController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/TempDataController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/TempDataController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/TempDataController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/TempDataController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/TempDataPropertyController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/TempDataPropertyController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/TempDataPropertyController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/TempDataPropertyController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/TestingController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/TestingController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/TestingController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/TestingController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/UsersController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/UsersController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/UsersController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/UsersController.cs diff --git a/test/WebSites/BasicWebSite/Controllers/ViewDataPropertyController.cs b/src/Mvc/test/WebSites/BasicWebSite/Controllers/ViewDataPropertyController.cs similarity index 100% rename from test/WebSites/BasicWebSite/Controllers/ViewDataPropertyController.cs rename to src/Mvc/test/WebSites/BasicWebSite/Controllers/ViewDataPropertyController.cs diff --git a/test/WebSites/BasicWebSite/Conventions/ApplicationDescription.cs b/src/Mvc/test/WebSites/BasicWebSite/Conventions/ApplicationDescription.cs similarity index 100% rename from test/WebSites/BasicWebSite/Conventions/ApplicationDescription.cs rename to src/Mvc/test/WebSites/BasicWebSite/Conventions/ApplicationDescription.cs diff --git a/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/RedirectAntiforgeryValidationFailedResultFilter.cs diff --git a/test/WebSites/BasicWebSite/Filters/RequestIdService.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/RequestIdService.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/RequestIdService.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/RequestIdService.cs diff --git a/test/WebSites/BasicWebSite/Filters/ServiceActionFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/ServiceActionFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/ServiceActionFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/ServiceActionFilter.cs diff --git a/test/WebSites/BasicWebSite/Filters/TestExceptionFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/TestExceptionFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/TestExceptionFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/TestExceptionFilter.cs diff --git a/test/WebSites/BasicWebSite/Filters/TraceOutputResultFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/TraceOutputResultFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/TraceOutputResultFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/TraceOutputResultFilter.cs diff --git a/test/WebSites/BasicWebSite/Filters/TraceResourceFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/TraceResourceFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/TraceResourceFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/TraceResourceFilter.cs diff --git a/test/WebSites/BasicWebSite/Filters/UnprocessableResultFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/Filters/UnprocessableResultFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Filters/UnprocessableResultFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Filters/UnprocessableResultFilter.cs diff --git a/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs b/src/Mvc/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Formatters/CustomFormatter.cs diff --git a/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs b/src/Mvc/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs similarity index 100% rename from test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs rename to src/Mvc/test/WebSites/BasicWebSite/Formatters/PlainTextFormatter.cs diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs b/src/Mvc/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs similarity index 100% rename from test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs rename to src/Mvc/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V3.cs diff --git a/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs b/src/Mvc/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs similarity index 100% rename from test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs rename to src/Mvc/test/WebSites/BasicWebSite/Formatters/VCardFormatter_V4.cs diff --git a/test/WebSites/BasicWebSite/LocalizationPipeline.cs b/src/Mvc/test/WebSites/BasicWebSite/LocalizationPipeline.cs similarity index 100% rename from test/WebSites/BasicWebSite/LocalizationPipeline.cs rename to src/Mvc/test/WebSites/BasicWebSite/LocalizationPipeline.cs diff --git a/test/WebSites/BasicWebSite/ManagerHandler.cs b/src/Mvc/test/WebSites/BasicWebSite/ManagerHandler.cs similarity index 100% rename from test/WebSites/BasicWebSite/ManagerHandler.cs rename to src/Mvc/test/WebSites/BasicWebSite/ManagerHandler.cs diff --git a/test/WebSites/BasicWebSite/Models/Contact.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Contact.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Contact.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Contact.cs diff --git a/test/WebSites/BasicWebSite/Models/GenderType.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/GenderType.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/GenderType.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/GenderType.cs diff --git a/test/WebSites/BasicWebSite/Models/LoginViewModel.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/LoginViewModel.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/LoginViewModel.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/LoginViewModel.cs diff --git a/test/WebSites/BasicWebSite/Models/Person.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Person.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Person.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Person.cs diff --git a/test/WebSites/BasicWebSite/Models/Product.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Product.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Product.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Product.cs diff --git a/test/WebSites/BasicWebSite/Models/Product_Json.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Product_Json.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Product_Json.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Product_Json.cs diff --git a/test/WebSites/BasicWebSite/Models/Product_Xml.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Product_Xml.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Product_Xml.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Product_Xml.cs diff --git a/test/WebSites/BasicWebSite/Models/Product_text.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/Product_text.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/Product_text.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/Product_text.cs diff --git a/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/RemoteAttributeUser.cs diff --git a/test/WebSites/BasicWebSite/Models/User.cs b/src/Mvc/test/WebSites/BasicWebSite/Models/User.cs similarity index 100% rename from test/WebSites/BasicWebSite/Models/User.cs rename to src/Mvc/test/WebSites/BasicWebSite/Models/User.cs diff --git a/test/WebSites/BasicWebSite/Operations.cs b/src/Mvc/test/WebSites/BasicWebSite/Operations.cs similarity index 100% rename from test/WebSites/BasicWebSite/Operations.cs rename to src/Mvc/test/WebSites/BasicWebSite/Operations.cs diff --git a/test/WebSites/BasicWebSite/Program.cs b/src/Mvc/test/WebSites/BasicWebSite/Program.cs similarity index 100% rename from test/WebSites/BasicWebSite/Program.cs rename to src/Mvc/test/WebSites/BasicWebSite/Program.cs diff --git a/test/WebSites/BasicWebSite/RequestIdMiddleware.cs b/src/Mvc/test/WebSites/BasicWebSite/RequestIdMiddleware.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestIdMiddleware.cs rename to src/Mvc/test/WebSites/BasicWebSite/RequestIdMiddleware.cs diff --git a/test/WebSites/BasicWebSite/RequestIdViewComponent.cs b/src/Mvc/test/WebSites/BasicWebSite/RequestIdViewComponent.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestIdViewComponent.cs rename to src/Mvc/test/WebSites/BasicWebSite/RequestIdViewComponent.cs diff --git a/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs b/src/Mvc/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs rename to src/Mvc/test/WebSites/BasicWebSite/RequestScopedActionConstraint.cs diff --git a/test/WebSites/BasicWebSite/RequestScopedFilter.cs b/src/Mvc/test/WebSites/BasicWebSite/RequestScopedFilter.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestScopedFilter.cs rename to src/Mvc/test/WebSites/BasicWebSite/RequestScopedFilter.cs diff --git a/test/WebSites/BasicWebSite/RequestScopedTagHelper.cs b/src/Mvc/test/WebSites/BasicWebSite/RequestScopedTagHelper.cs similarity index 100% rename from test/WebSites/BasicWebSite/RequestScopedTagHelper.cs rename to src/Mvc/test/WebSites/BasicWebSite/RequestScopedTagHelper.cs diff --git a/test/WebSites/BasicWebSite/Startup.cs b/src/Mvc/test/WebSites/BasicWebSite/Startup.cs similarity index 100% rename from test/WebSites/BasicWebSite/Startup.cs rename to src/Mvc/test/WebSites/BasicWebSite/Startup.cs diff --git a/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs similarity index 100% rename from test/WebSites/BasicWebSite/StartupRequestLimitSize.cs rename to src/Mvc/test/WebSites/BasicWebSite/StartupRequestLimitSize.cs diff --git a/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs similarity index 100% rename from test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs rename to src/Mvc/test/WebSites/BasicWebSite/StartupWithCookieTempDataProviderAndCookieConsent.cs diff --git a/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs similarity index 100% rename from test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs rename to src/Mvc/test/WebSites/BasicWebSite/StartupWithCustomInvalidModelStateFactory.cs diff --git a/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs similarity index 100% rename from test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs rename to src/Mvc/test/WebSites/BasicWebSite/StartupWithEndpointRouting.cs diff --git a/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs b/src/Mvc/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs similarity index 100% rename from test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs rename to src/Mvc/test/WebSites/BasicWebSite/StartupWithSessionTempDataProvider.cs diff --git a/test/WebSites/BasicWebSite/StoreIntoTempDataActionResult.cs b/src/Mvc/test/WebSites/BasicWebSite/StoreIntoTempDataActionResult.cs similarity index 100% rename from test/WebSites/BasicWebSite/StoreIntoTempDataActionResult.cs rename to src/Mvc/test/WebSites/BasicWebSite/StoreIntoTempDataActionResult.cs diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/AntiforgeryTokenAndResponseCaching.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/AntiforgeryTokenAndResponseCaching.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/AntiforgeryTokenAndResponseCaching.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/AntiforgeryTokenAndResponseCaching.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/FlushAsyncLogin.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/FlushAsyncLogin.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/FlushAsyncLogin.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/FlushAsyncLogin.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/Index.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/Index.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/Index.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/Index.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/Login.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/Login.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/Login.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/Login.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/_FlushAsyncLayout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_FlushAsyncLayout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/_FlushAsyncLayout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_FlushAsyncLayout.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/_Layout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_Layout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/_Layout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_Layout.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Antiforgery/_LoginPartial.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_LoginPartial.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Antiforgery/_LoginPartial.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Antiforgery/_LoginPartial.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/ActionLinkView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/ActionLinkView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/ActionLinkView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/ActionLinkView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/CSharp7View.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/CSharp7View.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/CSharp7View.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/CSharp7View.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/Index.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/Index.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/Index.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/JsonHelperInView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/JsonHelperInView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/JsonHelperInView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/JsonHelperInView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/JsonHelperWithSettingsInView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/JsonHelperWithSettingsInView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/JsonHelperWithSettingsInView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/JsonHelperWithSettingsInView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/PlainView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/PlainView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/PlainView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/PlainView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Home/ViewWithPrefixedAttributeValue.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Home/ViewWithPrefixedAttributeValue.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Home/ViewWithPrefixedAttributeValue.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Home/ViewWithPrefixedAttributeValue.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnOtherController.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnOtherController.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnOtherController.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnOtherController.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnSameController.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnSameController.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnSameController.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_ActionOnSameController.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/ActionLink_HostNameFragmentAttributes.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_HostNameFragmentAttributes.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/ActionLink_HostNameFragmentAttributes.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_HostNameFragmentAttributes.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/ActionLink_SecurePage_ImplicitHostName.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_SecurePage_ImplicitHostName.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/ActionLink_SecurePage_ImplicitHostName.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/ActionLink_SecurePage_ImplicitHostName.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/RouteLink_HostNameFragmentAttributes.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_HostNameFragmentAttributes.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/RouteLink_HostNameFragmentAttributes.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_HostNameFragmentAttributes.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/RouteLink_RestLinkToOtherController.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_RestLinkToOtherController.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/RouteLink_RestLinkToOtherController.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_RestLinkToOtherController.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Links/RouteLink_SecureApi_ImplicitHostName.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_SecureApi_ImplicitHostName.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Links/RouteLink_SecureApi_ImplicitHostName.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Links/RouteLink_SecureApi_ImplicitHostName.cshtml diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/PageRoute/AttributeRouteView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/PageRoute/ConventionalRouteView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/Create.cshtml diff --git a/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/RemoteAttribute_Home/_Layout.cshtml diff --git a/test/WebSites/BasicWebSite/Views/RequestScopedService/TagHelper.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/TagHelper.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/RequestScopedService/TagHelper.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/TagHelper.cshtml diff --git a/test/WebSites/BasicWebSite/Views/RequestScopedService/View.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/View.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/RequestScopedService/View.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/View.cshtml diff --git a/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/RequestScopedService/ViewComponent.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Shared/Components/PassThrough/Default.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Components/PassThrough/Default.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Shared/Components/PassThrough/Default.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Components/PassThrough/Default.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Shared/Components/ViewData/Default.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Components/ViewData/Default.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Shared/Components/ViewData/Default.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Components/ViewData/Default.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Shared/Error.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Error.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Shared/Error.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Shared/Error.cshtml diff --git a/test/WebSites/BasicWebSite/Views/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/Shared/_Layout.cshtml diff --git a/test/WebSites/BasicWebSite/Views/TempData/DisplayTempData.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/TempData/DisplayTempData.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/TempData/DisplayTempData.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/TempData/DisplayTempData.cshtml diff --git a/test/WebSites/BasicWebSite/Views/TempData/Index.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/TempData/Index.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/TempData/Index.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/TempData/Index.cshtml diff --git a/test/WebSites/BasicWebSite/Views/TempDataProperty/DetailsView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/TempDataProperty/DetailsView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/TempDataProperty/DetailsView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/TempDataProperty/DetailsView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataInViewComponent.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataInViewComponent.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataInViewComponent.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataInViewComponent.cshtml diff --git a/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataPropertyToView.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataPropertyToView.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataPropertyToView.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/ViewDataPropertyToView.cshtml diff --git a/test/WebSites/BasicWebSite/Views/ViewDataProperty/_Layout.cshtml b/src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/_Layout.cshtml similarity index 100% rename from test/WebSites/BasicWebSite/Views/ViewDataProperty/_Layout.cshtml rename to src/Mvc/test/WebSites/BasicWebSite/Views/ViewDataProperty/_Layout.cshtml diff --git a/test/WebSites/BasicWebSite/VndErrorAttribute.cs b/src/Mvc/test/WebSites/BasicWebSite/VndErrorAttribute.cs similarity index 100% rename from test/WebSites/BasicWebSite/VndErrorAttribute.cs rename to src/Mvc/test/WebSites/BasicWebSite/VndErrorAttribute.cs diff --git a/test/WebSites/BasicWebSite/_bower.json b/src/Mvc/test/WebSites/BasicWebSite/_bower.json similarity index 100% rename from test/WebSites/BasicWebSite/_bower.json rename to src/Mvc/test/WebSites/BasicWebSite/_bower.json diff --git a/test/WebSites/BasicWebSite/_bower.readme b/src/Mvc/test/WebSites/BasicWebSite/_bower.readme similarity index 100% rename from test/WebSites/BasicWebSite/_bower.readme rename to src/Mvc/test/WebSites/BasicWebSite/_bower.readme diff --git a/test/WebSites/BasicWebSite/_gruntfile.js b/src/Mvc/test/WebSites/BasicWebSite/_gruntfile.js similarity index 100% rename from test/WebSites/BasicWebSite/_gruntfile.js rename to src/Mvc/test/WebSites/BasicWebSite/_gruntfile.js diff --git a/test/WebSites/BasicWebSite/_package.json b/src/Mvc/test/WebSites/BasicWebSite/_package.json similarity index 100% rename from test/WebSites/BasicWebSite/_package.json rename to src/Mvc/test/WebSites/BasicWebSite/_package.json diff --git a/test/WebSites/BasicWebSite/readme.md b/src/Mvc/test/WebSites/BasicWebSite/readme.md similarity index 100% rename from test/WebSites/BasicWebSite/readme.md rename to src/Mvc/test/WebSites/BasicWebSite/readme.md diff --git a/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js b/src/Mvc/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js similarity index 100% rename from test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js rename to src/Mvc/test/WebSites/BasicWebSite/wwwroot/lib/jquery-validation-unobtrusive/jquery.validate.unobtrusive.min.js diff --git a/test/WebSites/Common/TestResponseGenerator.cs b/src/Mvc/test/WebSites/Common/TestResponseGenerator.cs similarity index 100% rename from test/WebSites/Common/TestResponseGenerator.cs rename to src/Mvc/test/WebSites/Common/TestResponseGenerator.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ClientUIStubController.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ControllerWithConstructorInjection.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ControllerWithConstructorInjection.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/ControllerWithConstructorInjection.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ControllerWithConstructorInjection.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ControllersFromServicesClassLibrary.csproj b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ControllersFromServicesClassLibrary.csproj similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/ControllersFromServicesClassLibrary.csproj rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ControllersFromServicesClassLibrary.csproj diff --git a/test/WebSites/ControllersFromServicesClassLibrary/EmployeeRecords.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/EmployeeRecords.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/EmployeeRecords.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/EmployeeRecords.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/GenericController.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/GenericController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/GenericController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/GenericController.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/Inventory.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/NestedControllerOwner.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/NestedControllerOwner.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/NestedControllerOwner.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/NestedControllerOwner.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/QueryValueService.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/QueryValueService.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/QueryValueService.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/QueryValueService.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/ResourcesController.cs diff --git a/test/WebSites/ControllersFromServicesClassLibrary/TimeScheduleController.cs b/src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/TimeScheduleController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesClassLibrary/TimeScheduleController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesClassLibrary/TimeScheduleController.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/AnotherController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/AnotherController.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/AssemblyMetadataReferenceFeatureProvider.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/Components/ComponentFromServicesViewComponent.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/ControllersFromServicesWebSite.csproj diff --git a/test/WebSites/ControllersFromServicesWebSite/NotInServicesController.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/NotInServicesController.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/NotInServicesController.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/NotInServicesController.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/Startup.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/Startup.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/Startup.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/Startup.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/TagHelpers/InServicesTagHelper.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/TagHelpers/InServicesTagHelper.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/TagHelpers/InServicesTagHelper.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/TagHelpers/InServicesTagHelper.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/ValueService.cs b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/ValueService.cs similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/ValueService.cs rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/ValueService.cs diff --git a/test/WebSites/ControllersFromServicesWebSite/ViewData.cshtml b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/ViewData.cshtml similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/ViewData.cshtml rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/ViewData.cshtml diff --git a/test/WebSites/ControllersFromServicesWebSite/Views/Another/InServicesTagHelper.cshtml b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/Views/Another/InServicesTagHelper.cshtml similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/Views/Another/InServicesTagHelper.cshtml rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/Views/Another/InServicesTagHelper.cshtml diff --git a/test/WebSites/ControllersFromServicesWebSite/readme.md b/src/Mvc/test/WebSites/ControllersFromServicesWebSite/readme.md similarity index 100% rename from test/WebSites/ControllersFromServicesWebSite/readme.md rename to src/Mvc/test/WebSites/ControllersFromServicesWebSite/readme.md diff --git a/test/WebSites/CorsWebSite/Controllers/BlogController.cs b/src/Mvc/test/WebSites/CorsWebSite/Controllers/BlogController.cs similarity index 100% rename from test/WebSites/CorsWebSite/Controllers/BlogController.cs rename to src/Mvc/test/WebSites/CorsWebSite/Controllers/BlogController.cs diff --git a/test/WebSites/CorsWebSite/Controllers/CustomerController.cs b/src/Mvc/test/WebSites/CorsWebSite/Controllers/CustomerController.cs similarity index 100% rename from test/WebSites/CorsWebSite/Controllers/CustomerController.cs rename to src/Mvc/test/WebSites/CorsWebSite/Controllers/CustomerController.cs diff --git a/test/WebSites/CorsWebSite/Controllers/StoreController.cs b/src/Mvc/test/WebSites/CorsWebSite/Controllers/StoreController.cs similarity index 100% rename from test/WebSites/CorsWebSite/Controllers/StoreController.cs rename to src/Mvc/test/WebSites/CorsWebSite/Controllers/StoreController.cs diff --git a/test/WebSites/CorsWebSite/CorsWebSite.csproj b/src/Mvc/test/WebSites/CorsWebSite/CorsWebSite.csproj similarity index 100% rename from test/WebSites/CorsWebSite/CorsWebSite.csproj rename to src/Mvc/test/WebSites/CorsWebSite/CorsWebSite.csproj diff --git a/test/WebSites/CorsWebSite/Filters/AllRequestsBlockingAuthorizationFilter.cs b/src/Mvc/test/WebSites/CorsWebSite/Filters/AllRequestsBlockingAuthorizationFilter.cs similarity index 100% rename from test/WebSites/CorsWebSite/Filters/AllRequestsBlockingAuthorizationFilter.cs rename to src/Mvc/test/WebSites/CorsWebSite/Filters/AllRequestsBlockingAuthorizationFilter.cs diff --git a/test/WebSites/CorsWebSite/Program.cs b/src/Mvc/test/WebSites/CorsWebSite/Program.cs similarity index 100% rename from test/WebSites/CorsWebSite/Program.cs rename to src/Mvc/test/WebSites/CorsWebSite/Program.cs diff --git a/test/WebSites/CorsWebSite/Startup.cs b/src/Mvc/test/WebSites/CorsWebSite/Startup.cs similarity index 100% rename from test/WebSites/CorsWebSite/Startup.cs rename to src/Mvc/test/WebSites/CorsWebSite/Startup.cs diff --git a/test/WebSites/CorsWebSite/StartupWith21Compat.cs b/src/Mvc/test/WebSites/CorsWebSite/StartupWith21Compat.cs similarity index 100% rename from test/WebSites/CorsWebSite/StartupWith21Compat.cs rename to src/Mvc/test/WebSites/CorsWebSite/StartupWith21Compat.cs diff --git a/test/WebSites/CorsWebSite/readme.md b/src/Mvc/test/WebSites/CorsWebSite/readme.md similarity index 100% rename from test/WebSites/CorsWebSite/readme.md rename to src/Mvc/test/WebSites/CorsWebSite/readme.md diff --git a/test/WebSites/Directory.Build.props b/src/Mvc/test/WebSites/Directory.Build.props similarity index 100% rename from test/WebSites/Directory.Build.props rename to src/Mvc/test/WebSites/Directory.Build.props diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/AggregateExceptionController.cs b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/AggregateExceptionController.cs similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/AggregateExceptionController.cs rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/AggregateExceptionController.cs diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareController.cs diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareWebSite.csproj b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareWebSite.csproj similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareWebSite.csproj rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/ErrorPageMiddlewareWebSite.csproj diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/README.md b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/README.md similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/README.md rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/README.md diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Startup.cs diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/Index.cshtml b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/Index.cshtml similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/Index.cshtml rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/Index.cshtml diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/_ViewImports.cshtml b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/_ViewImports.cshtml similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/_ViewImports.cshtml rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorFromViewImports/_ViewImports.cshtml diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/CompilationFailure.cshtml b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/CompilationFailure.cshtml similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/CompilationFailure.cshtml rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/CompilationFailure.cshtml diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/ParserError.cshtml b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/ParserError.cshtml similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/ParserError.cshtml rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/ParserError.cshtml diff --git a/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/RuntimeError.cshtml b/src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/RuntimeError.cshtml similarity index 100% rename from test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/RuntimeError.cshtml rename to src/Mvc/test/WebSites/ErrorPageMiddlewareWebSite/Views/ErrorPageMiddleware/RuntimeError.cshtml diff --git a/test/WebSites/FSharpWebSite/Controllers/HomeController.fs b/src/Mvc/test/WebSites/FSharpWebSite/Controllers/HomeController.fs similarity index 100% rename from test/WebSites/FSharpWebSite/Controllers/HomeController.fs rename to src/Mvc/test/WebSites/FSharpWebSite/Controllers/HomeController.fs diff --git a/test/WebSites/FSharpWebSite/FSharpWebSite.fsproj b/src/Mvc/test/WebSites/FSharpWebSite/FSharpWebSite.fsproj similarity index 100% rename from test/WebSites/FSharpWebSite/FSharpWebSite.fsproj rename to src/Mvc/test/WebSites/FSharpWebSite/FSharpWebSite.fsproj diff --git a/test/WebSites/FSharpWebSite/Program.fs b/src/Mvc/test/WebSites/FSharpWebSite/Program.fs similarity index 100% rename from test/WebSites/FSharpWebSite/Program.fs rename to src/Mvc/test/WebSites/FSharpWebSite/Program.fs diff --git a/test/WebSites/FSharpWebSite/Startup.fs b/src/Mvc/test/WebSites/FSharpWebSite/Startup.fs similarity index 100% rename from test/WebSites/FSharpWebSite/Startup.fs rename to src/Mvc/test/WebSites/FSharpWebSite/Startup.fs diff --git a/test/WebSites/FSharpWebSite/Views/Home/Index.cshtml b/src/Mvc/test/WebSites/FSharpWebSite/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/FSharpWebSite/Views/Home/Index.cshtml rename to src/Mvc/test/WebSites/FSharpWebSite/Views/Home/Index.cshtml diff --git a/test/WebSites/FSharpWebSite/Views/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/FSharpWebSite/Views/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/FSharpWebSite/Views/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/FSharpWebSite/Views/Shared/_Layout.cshtml diff --git a/test/WebSites/FSharpWebSite/Views/_ViewImports.cshtml b/src/Mvc/test/WebSites/FSharpWebSite/Views/_ViewImports.cshtml similarity index 100% rename from test/WebSites/FSharpWebSite/Views/_ViewImports.cshtml rename to src/Mvc/test/WebSites/FSharpWebSite/Views/_ViewImports.cshtml diff --git a/test/WebSites/FSharpWebSite/Views/_ViewStart.cshtml b/src/Mvc/test/WebSites/FSharpWebSite/Views/_ViewStart.cshtml similarity index 100% rename from test/WebSites/FSharpWebSite/Views/_ViewStart.cshtml rename to src/Mvc/test/WebSites/FSharpWebSite/Views/_ViewStart.cshtml diff --git a/test/WebSites/FilesWebSite/Controllers/DownloadFilesController.cs b/src/Mvc/test/WebSites/FilesWebSite/Controllers/DownloadFilesController.cs similarity index 100% rename from test/WebSites/FilesWebSite/Controllers/DownloadFilesController.cs rename to src/Mvc/test/WebSites/FilesWebSite/Controllers/DownloadFilesController.cs diff --git a/test/WebSites/FilesWebSite/Controllers/EmbeddedFilesController.cs b/src/Mvc/test/WebSites/FilesWebSite/Controllers/EmbeddedFilesController.cs similarity index 100% rename from test/WebSites/FilesWebSite/Controllers/EmbeddedFilesController.cs rename to src/Mvc/test/WebSites/FilesWebSite/Controllers/EmbeddedFilesController.cs diff --git a/test/WebSites/FilesWebSite/Controllers/UploadFilesController.cs b/src/Mvc/test/WebSites/FilesWebSite/Controllers/UploadFilesController.cs similarity index 100% rename from test/WebSites/FilesWebSite/Controllers/UploadFilesController.cs rename to src/Mvc/test/WebSites/FilesWebSite/Controllers/UploadFilesController.cs diff --git a/test/WebSites/FilesWebSite/EmbeddedResources/Greetings.txt b/src/Mvc/test/WebSites/FilesWebSite/EmbeddedResources/Greetings.txt similarity index 100% rename from test/WebSites/FilesWebSite/EmbeddedResources/Greetings.txt rename to src/Mvc/test/WebSites/FilesWebSite/EmbeddedResources/Greetings.txt diff --git a/test/WebSites/FilesWebSite/FilesWebSite.csproj b/src/Mvc/test/WebSites/FilesWebSite/FilesWebSite.csproj similarity index 100% rename from test/WebSites/FilesWebSite/FilesWebSite.csproj rename to src/Mvc/test/WebSites/FilesWebSite/FilesWebSite.csproj diff --git a/test/WebSites/FilesWebSite/Models/Product.cs b/src/Mvc/test/WebSites/FilesWebSite/Models/Product.cs similarity index 100% rename from test/WebSites/FilesWebSite/Models/Product.cs rename to src/Mvc/test/WebSites/FilesWebSite/Models/Product.cs diff --git a/test/WebSites/FilesWebSite/Models/User.cs b/src/Mvc/test/WebSites/FilesWebSite/Models/User.cs similarity index 100% rename from test/WebSites/FilesWebSite/Models/User.cs rename to src/Mvc/test/WebSites/FilesWebSite/Models/User.cs diff --git a/test/WebSites/FilesWebSite/Startup.cs b/src/Mvc/test/WebSites/FilesWebSite/Startup.cs similarity index 100% rename from test/WebSites/FilesWebSite/Startup.cs rename to src/Mvc/test/WebSites/FilesWebSite/Startup.cs diff --git a/test/WebSites/FilesWebSite/readme.md b/src/Mvc/test/WebSites/FilesWebSite/readme.md similarity index 100% rename from test/WebSites/FilesWebSite/readme.md rename to src/Mvc/test/WebSites/FilesWebSite/readme.md diff --git a/test/WebSites/FilesWebSite/sample.txt b/src/Mvc/test/WebSites/FilesWebSite/sample.txt similarity index 100% rename from test/WebSites/FilesWebSite/sample.txt rename to src/Mvc/test/WebSites/FilesWebSite/sample.txt diff --git a/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/DataContractSerializerController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/DoNotRespectBrowserAcceptHeaderController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/DoNotRespectBrowserAcceptHeaderController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/DoNotRespectBrowserAcceptHeaderController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/DoNotRespectBrowserAcceptHeaderController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/InputFormatterController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonFormatterController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/JsonPatchController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonPatchController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/JsonPatchController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/JsonPatchController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/PolymorphicBindingController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/PolymorphicPropertyBindingController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/RespectBrowserAcceptHeaderController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/RespectBrowserAcceptHeaderController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/RespectBrowserAcceptHeaderController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/RespectBrowserAcceptHeaderController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/SerializableErrorController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/StreamController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/StreamController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/StreamController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/StreamController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/TestApiController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/TestApiController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/TopLevelValidationController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/TopLevelValidationController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/TopLevelValidationController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/TopLevelValidationController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/ValidationController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/ValidationController.cs diff --git a/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs b/src/Mvc/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Controllers/XmlSerializerController.cs diff --git a/test/WebSites/FormatterWebSite/Filters/ModelStateValidationFilterAttribute.cs b/src/Mvc/test/WebSites/FormatterWebSite/Filters/ModelStateValidationFilterAttribute.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Filters/ModelStateValidationFilterAttribute.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Filters/ModelStateValidationFilterAttribute.cs diff --git a/test/WebSites/FormatterWebSite/FormatterWebSite.csproj b/src/Mvc/test/WebSites/FormatterWebSite/FormatterWebSite.csproj similarity index 100% rename from test/WebSites/FormatterWebSite/FormatterWebSite.csproj rename to src/Mvc/test/WebSites/FormatterWebSite/FormatterWebSite.csproj diff --git a/test/WebSites/FormatterWebSite/IModelConverter.cs b/src/Mvc/test/WebSites/FormatterWebSite/IModelConverter.cs similarity index 100% rename from test/WebSites/FormatterWebSite/IModelConverter.cs rename to src/Mvc/test/WebSites/FormatterWebSite/IModelConverter.cs diff --git a/test/WebSites/FormatterWebSite/Models/BaseModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/BaseModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/BaseModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/BaseModel.cs diff --git a/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/BookModelWithNoValidation.cs diff --git a/test/WebSites/FormatterWebSite/Models/DerivedDummyClass.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/DerivedDummyClass.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/DerivedDummyClass.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/DerivedDummyClass.cs diff --git a/test/WebSites/FormatterWebSite/Models/DerivedModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/DerivedModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/DerivedModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/DerivedModel.cs diff --git a/test/WebSites/FormatterWebSite/Models/Developer.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Developer.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Developer.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Developer.cs diff --git a/test/WebSites/FormatterWebSite/Models/DummyClass.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/DummyClass.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/DummyClass.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/DummyClass.cs diff --git a/test/WebSites/FormatterWebSite/Models/Employee.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Employee.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Employee.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Employee.cs diff --git a/test/WebSites/FormatterWebSite/Models/ErrorInfo.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/ErrorInfo.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/ErrorInfo.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/ErrorInfo.cs diff --git a/test/WebSites/FormatterWebSite/Models/IModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/IModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/IModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/IModel.cs diff --git a/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/InfinitelyRecursiveModel.cs diff --git a/test/WebSites/FormatterWebSite/Models/Person.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Person.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Person.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Person.cs diff --git a/test/WebSites/FormatterWebSite/Models/Product.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Product.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Product.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Product.cs diff --git a/test/WebSites/FormatterWebSite/Models/Project.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Project.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Project.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Project.cs diff --git a/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/RecursiveIdentifier.cs diff --git a/test/WebSites/FormatterWebSite/Models/Review.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/Review.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/Review.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/Review.cs diff --git a/test/WebSites/FormatterWebSite/Models/SimpleTypePropertiesModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/SimpleTypePropertiesModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/SimpleTypePropertiesModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/SimpleTypePropertiesModel.cs diff --git a/test/WebSites/FormatterWebSite/Models/User.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/User.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/User.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/User.cs diff --git a/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs b/src/Mvc/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Models/ValidationProviderAttributeModel.cs diff --git a/test/WebSites/FormatterWebSite/PolymorphicBinder.cs b/src/Mvc/test/WebSites/FormatterWebSite/PolymorphicBinder.cs similarity index 100% rename from test/WebSites/FormatterWebSite/PolymorphicBinder.cs rename to src/Mvc/test/WebSites/FormatterWebSite/PolymorphicBinder.cs diff --git a/test/WebSites/FormatterWebSite/Program.cs b/src/Mvc/test/WebSites/FormatterWebSite/Program.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Program.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Program.cs diff --git a/test/WebSites/FormatterWebSite/Startup.cs b/src/Mvc/test/WebSites/FormatterWebSite/Startup.cs similarity index 100% rename from test/WebSites/FormatterWebSite/Startup.cs rename to src/Mvc/test/WebSites/FormatterWebSite/Startup.cs diff --git a/test/WebSites/FormatterWebSite/StartupWithRespectBrowserAcceptHeader.cs b/src/Mvc/test/WebSites/FormatterWebSite/StartupWithRespectBrowserAcceptHeader.cs similarity index 100% rename from test/WebSites/FormatterWebSite/StartupWithRespectBrowserAcceptHeader.cs rename to src/Mvc/test/WebSites/FormatterWebSite/StartupWithRespectBrowserAcceptHeader.cs diff --git a/test/WebSites/FormatterWebSite/StringInputFormatter.cs b/src/Mvc/test/WebSites/FormatterWebSite/StringInputFormatter.cs similarity index 100% rename from test/WebSites/FormatterWebSite/StringInputFormatter.cs rename to src/Mvc/test/WebSites/FormatterWebSite/StringInputFormatter.cs diff --git a/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs b/src/Mvc/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs similarity index 100% rename from test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs rename to src/Mvc/test/WebSites/FormatterWebSite/ValidateBodyParameterAttribute.cs diff --git a/test/WebSites/FormatterWebSite/readme.md b/src/Mvc/test/WebSites/FormatterWebSite/readme.md similarity index 100% rename from test/WebSites/FormatterWebSite/readme.md rename to src/Mvc/test/WebSites/FormatterWebSite/readme.md diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Controllers/HtmlGeneration_CustomerController.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Controllers/HtmlGeneration_CustomerController.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Areas/Customer/Controllers/HtmlGeneration_CustomerController.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Controllers/HtmlGeneration_CustomerController.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithFallback.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/PartialWithOptional.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_Fallback.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Areas/Customer/Pages/_ViewImports.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData - LackModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData - LackModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Components/CheckViewData - LackModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData - LackModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Components/CheckViewData.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/CheckViewData.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Components/SplashViewComponent.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/SplashViewComponent.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Components/SplashViewComponent.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Components/SplashViewComponent.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/Catalog_CacheTagHelperController.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/CheckViewData.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/CheckViewData.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/CheckViewData.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/CheckViewData.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_HomeController.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_OrderController.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_OrderController.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_OrderController.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_OrderController.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_ProductController.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_ProductController.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_ProductController.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_ProductController.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_WeirdExpressions.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_WeirdExpressions.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_WeirdExpressions.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Controllers/HtmlGeneration_WeirdExpressions.cs diff --git a/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj b/src/Mvc/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/HtmlGenerationWebSite.csproj diff --git a/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/ISignalTokenProviderService.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/AClass.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/AClass.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/AClass.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/AClass.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Customer.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Customer.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Customer.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Customer.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/DayOfWeek.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/DayOfWeek.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/DayOfWeek.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/DayOfWeek.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Employee.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Employee.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Employee.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Employee.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Folder.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Folder.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Folder.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Folder.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Gender.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Gender.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Gender.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Gender.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Item.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Item.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Item.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Item.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Month.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Month.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Month.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Month.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Order.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Order.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Order.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Order.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/PartialModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/PartialModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/PartialModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/PartialModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Person.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Person.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Person.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Person.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Product.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Product.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Product.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Product.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/ProductRecommendations.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ProductRecommendations.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/ProductRecommendations.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ProductRecommendations.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/StatusMessageModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/StatusMessageModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/StatusMessageModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/StatusMessageModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/SuperTemplateModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/SuperTemplateModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/SuperTemplateModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/SuperTemplateModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/SuperViewModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/SuperViewModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/SuperViewModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/SuperViewModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/TemplateModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/TemplateModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/TemplateModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/TemplateModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ValidationProviderAttributeModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/ViewModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ViewModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/ViewModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/ViewModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/Warehouse.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Warehouse.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/Warehouse.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/Warehouse.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Models/WeirdModel.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/WeirdModel.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Models/WeirdModel.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Models/WeirdModel.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Pages/CacheTagHelper_VaryByCulture.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Pages/_ViewImports.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/ProductsService.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/ProductsService.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/ProductsService.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/ProductsService.cs diff --git a/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/SignalTokenProviderService.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Startup.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Startup.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Startup.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Startup.cs diff --git a/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/StartupWith21CompatibilityBehavior.cs diff --git a/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/StartupWithCultureReplace.cs diff --git a/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs b/src/Mvc/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/TestCacheTagHelper.cs diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ConfirmPayment.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Deals.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Deals.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Deals.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Deals.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Details.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Details.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Details.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Details.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ListCategories.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/PastPurchases.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/ShoppingCart.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/Splash.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_SplashPartial.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_ViewImports.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_ViewImports.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_ViewImports.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Catalog_CacheTagHelper/_ViewImports.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/AtViewModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/AtViewModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/AtViewModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/AtViewModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData/Default.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData/Default.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData/Default.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData/Default.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData___LackModel/Default.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData___LackModel/Default.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData___LackModel/Default.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/Components/CheckViewData___LackModel/Default.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32 - LackModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32 - LackModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32 - LackModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32 - LackModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int32.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64 - LackModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64 - LackModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64 - LackModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64 - LackModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/Int64.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/LackModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/LackModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/LackModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/LackModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/TemplateModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/TemplateModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/TemplateModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/TemplateModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/ViewModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/ViewModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/ViewModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/DisplayTemplates/ViewModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/PartialForViewModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/PartialForViewModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/PartialForViewModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/PartialForViewModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/ViewModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/ViewModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/ViewModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/CheckViewData/ViewModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/AttributesWithBooleanValues.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/AttributesWithBooleanValues.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/AttributesWithBooleanValues.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/AttributesWithBooleanValues.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/CreateWarehouse.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/CreateWarehouse.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/CreateWarehouse.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/CreateWarehouse.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditWarehouse.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditWarehouse.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditWarehouse.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditWarehouse.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/Employee.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/Employee.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/Employee.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/Employee.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingHtmlHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingHtmlHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingHtmlHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingHtmlHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingTagHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingTagHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingTagHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EditorTemplates/GenderUsingTagHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EmployeeList.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EmployeeList.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EmployeeList.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/EmployeeList.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Enum.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Enum.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Enum.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Enum.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Environment.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Environment.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Environment.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Environment.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Form.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Form.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Form.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Form.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Image.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Image.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Image.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Image.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Index.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Input.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Input.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Input.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Input.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingModelSpecificEditorTemplate.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingModelSpecificEditorTemplate.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingModelSpecificEditorTemplate.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingModelSpecificEditorTemplate.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingSharedEditorTemplate.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingSharedEditorTemplate.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingSharedEditorTemplate.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ItemUsingSharedEditorTemplate.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Link.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Order.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Order.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Order.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Order.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/OrderUsingHtmlHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/OrderUsingHtmlHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/OrderUsingHtmlHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/OrderUsingHtmlHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/PartialTagHelperWithoutModel.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/PartialTagHelperWithoutModel.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/PartialTagHelperWithoutModel.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/PartialTagHelperWithoutModel.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Product.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Product.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Product.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Product.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductList.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductList.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductList.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductList.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductListUsingTagHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductListUsingTagHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductListUsingTagHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ProductListUsingTagHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Script.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Script.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Script.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Script.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/StatusMessage.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/StatusMessage.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/StatusMessage.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/StatusMessage.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/ValidationProviderAttribute.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Warehouse.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Warehouse.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Warehouse.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/Warehouse.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_EmployeePartial.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_EmployeePartial.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_EmployeePartial.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_EmployeePartial.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductPartial.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductPartial.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductPartial.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductPartial.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductRecommendations.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductRecommendations.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductRecommendations.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_ProductRecommendations.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_StatusMessagePartial.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_StatusMessagePartial.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_StatusMessagePartial.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_Home/_StatusMessagePartial.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithHtmlHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithHtmlHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithHtmlHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithHtmlHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithTagHelpers.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithTagHelpers.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithTagHelpers.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/HtmlGeneration_WeirdExpressions/GetWeirdWithTagHelpers.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Splash/Default.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Splash/Default.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Splash/Default.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/Components/Splash/Default.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/Customer.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/Customer.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/Customer.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/Customer.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/DisplayTemplates/DayOfWeek.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/DisplayTemplates/DayOfWeek.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/DisplayTemplates/DayOfWeek.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/DisplayTemplates/DayOfWeek.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/Common.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/Common.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/Common.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/Common.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/String.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/String.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/String.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/EditorTemplates/String.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/Views/Shared/_Partial.cshtml b/src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/_Partial.cshtml similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/Views/Shared/_Partial.cshtml rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/Views/Shared/_Partial.cshtml diff --git a/test/WebSites/HtmlGenerationWebSite/appRoot.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/appRoot.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/appRoot.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/appRoot.css diff --git a/test/WebSites/HtmlGenerationWebSite/appRoot.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/appRoot.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/appRoot.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/appRoot.js diff --git a/test/WebSites/HtmlGenerationWebSite/readme.md b/src/Mvc/test/WebSites/HtmlGenerationWebSite/readme.md similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/readme.md rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/readme.md diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/images/red.png b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/images/red.png similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/images/red.png rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/images/red.png diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/.gitattributes b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/.gitattributes similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/.gitattributes rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/.gitattributes diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.css diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/site.js diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.css diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/siteIntegrity.js diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.css diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site2.js diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.css diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.js diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.min.css b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.min.css similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.min.css rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/site3.min.css diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity2.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity2.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity2.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity2.js diff --git a/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity3.js b/src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity3.js similarity index 100% rename from test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity3.js rename to src/Mvc/test/WebSites/HtmlGenerationWebSite/wwwroot/styles/sub/siteIntegrity3.js diff --git a/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page.cs diff --git a/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page_Model.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page_Model.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page_Model.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Pages/Precompilation/Page_Model.cs diff --git a/test/WebSites/RazorBuildWebSite.PrecompiledViews/RazorBuildWebSite.PrecompiledViews.csproj b/src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/RazorBuildWebSite.PrecompiledViews.csproj similarity index 100% rename from test/WebSites/RazorBuildWebSite.PrecompiledViews/RazorBuildWebSite.PrecompiledViews.csproj rename to src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/RazorBuildWebSite.PrecompiledViews.csproj diff --git a/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Common/CommonView.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Common/CommonView.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Common/CommonView.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Common/CommonView.cs diff --git a/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Precompilation/View.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Precompilation/View.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Precompilation/View.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.PrecompiledViews/Views/Precompilation/View.cs diff --git a/test/WebSites/RazorBuildWebSite.Views/AssemblyInfo.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/AssemblyInfo.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/AssemblyInfo.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/AssemblyInfo.cs diff --git a/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page.cs diff --git a/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page_Model.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page_Model.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page_Model.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/Pages/Rzc/Page_Model.cs diff --git a/test/WebSites/RazorBuildWebSite.Views/RazorBuildWebSite.Views.csproj b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/RazorBuildWebSite.Views.csproj similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/RazorBuildWebSite.Views.csproj rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/RazorBuildWebSite.Views.csproj diff --git a/test/WebSites/RazorBuildWebSite.Views/Views/Common/CommonView.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/Views/Common/CommonView.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/Views/Common/CommonView.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/Views/Common/CommonView.cs diff --git a/test/WebSites/RazorBuildWebSite.Views/Views/Rzc/View.cs b/src/Mvc/test/WebSites/RazorBuildWebSite.Views/Views/Rzc/View.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite.Views/Views/Rzc/View.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite.Views/Views/Rzc/View.cs diff --git a/test/WebSites/RazorBuildWebSite/Controllers/CommonController.cs b/src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/CommonController.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite/Controllers/CommonController.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/CommonController.cs diff --git a/test/WebSites/RazorBuildWebSite/Controllers/PrecompilationController.cs b/src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/PrecompilationController.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite/Controllers/PrecompilationController.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/PrecompilationController.cs diff --git a/test/WebSites/RazorBuildWebSite/Controllers/RzcController.cs b/src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/RzcController.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite/Controllers/RzcController.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite/Controllers/RzcController.cs diff --git a/test/WebSites/RazorBuildWebSite/Pages/Precompilation/Page.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Pages/Precompilation/Page.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Pages/Precompilation/Page.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Pages/Precompilation/Page.cshtml diff --git a/test/WebSites/RazorBuildWebSite/Pages/Rzc/Page.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Pages/Rzc/Page.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Pages/Rzc/Page.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Pages/Rzc/Page.cshtml diff --git a/test/WebSites/RazorBuildWebSite/Pages/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Pages/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Pages/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Pages/_ViewImports.cshtml diff --git a/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj b/src/Mvc/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj similarity index 100% rename from test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj rename to src/Mvc/test/WebSites/RazorBuildWebSite/RazorBuildWebSite.csproj diff --git a/test/WebSites/RazorBuildWebSite/Startup.cs b/src/Mvc/test/WebSites/RazorBuildWebSite/Startup.cs similarity index 100% rename from test/WebSites/RazorBuildWebSite/Startup.cs rename to src/Mvc/test/WebSites/RazorBuildWebSite/Startup.cs diff --git a/test/WebSites/RazorBuildWebSite/Views/Precompilation/View.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Views/Precompilation/View.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Views/Precompilation/View.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Views/Precompilation/View.cshtml diff --git a/test/WebSites/RazorBuildWebSite/Views/Rzc/View.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Views/Rzc/View.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Views/Rzc/View.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Views/Rzc/View.cshtml diff --git a/test/WebSites/RazorBuildWebSite/Views/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorBuildWebSite/Views/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorBuildWebSite/Views/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorBuildWebSite/Views/_ViewImports.cshtml diff --git a/test/WebSites/RazorBuildWebSite/readme.md b/src/Mvc/test/WebSites/RazorBuildWebSite/readme.md similarity index 100% rename from test/WebSites/RazorBuildWebSite/readme.md rename to src/Mvc/test/WebSites/RazorBuildWebSite/readme.md diff --git a/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs b/src/Mvc/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs similarity index 100% rename from test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs rename to src/Mvc/test/WebSites/RazorPagesClassLibrary/ClassLibraryStartup.cs diff --git a/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml b/src/Mvc/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml similarity index 100% rename from test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml rename to src/Mvc/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Overriden.cshtml diff --git a/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml b/src/Mvc/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml similarity index 100% rename from test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml rename to src/Mvc/test/WebSites/RazorPagesClassLibrary/Pages/ClassLibraryPages/Served.cshtml diff --git a/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj b/src/Mvc/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj similarity index 100% rename from test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj rename to src/Mvc/test/WebSites/RazorPagesClassLibrary/RazorPagesClassLibrary.csproj diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Controllers/HomeController.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Controllers/HomeController.cs diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/About.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/About.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/About.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/About.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/RenderPartials.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/RenderPartials.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/RenderPartials.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/RenderPartials.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/_PartialInManage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/_PartialInManage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/_PartialInManage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/Manage/_PartialInManage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithLinks.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithLinks.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithLinks.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithLinks.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithRouteTemplate.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithRouteTemplate.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithRouteTemplate.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/PageWithRouteTemplate.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RelativeLinks/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RelativeLinks/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RelativeLinks/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RelativeLinks/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/AllowAnonymous.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/AllowAnonymous.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/AllowAnonymous.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/AllowAnonymous.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/RequiresAuth/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/_PartialInAreaPagesRoot.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/_PartialInAreaPagesRoot.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/_PartialInAreaPagesRoot.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Pages/_PartialInAreaPagesRoot.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Home/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Home/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Home/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Shared/_PartialInAreasSharedViews.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Shared/_PartialInAreasSharedViews.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Shared/_PartialInAreasSharedViews.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/Views/Shared/_PartialInAreasSharedViews.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Accounts/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Accounts/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Accounts/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Areas/Products/Pages/List.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Products/Pages/List.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Areas/Products/Pages/List.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Areas/Products/Pages/List.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Components/ViewDataViewComponent.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Components/ViewDataViewComponent.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Components/ViewDataViewComponent.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Components/ViewDataViewComponent.cs diff --git a/test/WebSites/RazorPagesWebSite/Controllers/RedirectController.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Controllers/RedirectController.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Controllers/RedirectController.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Controllers/RedirectController.cs diff --git a/test/WebSites/RazorPagesWebSite/Conventions/CustomModelTypeConvention.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Conventions/CustomModelTypeConvention.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Conventions/CustomModelTypeConvention.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Conventions/CustomModelTypeConvention.cs diff --git a/test/WebSites/RazorPagesWebSite/CustomActionResult.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/CustomActionResult.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/CustomActionResult.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/CustomActionResult.cs diff --git a/test/WebSites/RazorPagesWebSite/DefaultNamespace.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/DefaultNamespace.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/DefaultNamespace.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/DefaultNamespace.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HandlerTestPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HandlerTestPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HandlerTestPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HandlerTestPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorld.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorld.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorld.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorld.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithAuth.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithAuth.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithAuth.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithAuth.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithHandler.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithHandler.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithHandler.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithHandler.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithOptionsHandler.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cs diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelAttributeModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cs diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithPageModelHandler.cshtml diff --git a/test/WebSites/RazorPagesWebSite/HelloWorldWithRoute.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithRoute.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/HelloWorldWithRoute.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/HelloWorldWithRoute.cshtml diff --git a/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/InjectedPageProperties.cshtml diff --git a/test/WebSites/RazorPagesWebSite/ModelAsFilter.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelAsFilter.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelAsFilter.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelAsFilter.cs diff --git a/test/WebSites/RazorPagesWebSite/ModelAsFilter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelAsFilter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelAsFilter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelAsFilter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/ModelHandlerTestModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelHandlerTestModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelHandlerTestModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelHandlerTestModel.cs diff --git a/test/WebSites/RazorPagesWebSite/ModelHandlerTestPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelHandlerTestPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelHandlerTestPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelHandlerTestPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cs diff --git a/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithAuthFilter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cs diff --git a/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithPageFilter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cs diff --git a/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/ModelWithResponseCache.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Models/IUserModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Models/IUserModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Models/UserModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Models/UserModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Models/UserModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Models/UserModel.cs diff --git a/test/WebSites/RazorPagesWebSite/NoPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/NoPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/NoPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/NoPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/NonWatchingPhysicalFileProvider.cs diff --git a/test/WebSites/RazorPagesWebSite/OnGetView.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/OnGetView.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/OnGetView.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/OnGetView.cshtml diff --git a/test/WebSites/RazorPagesWebSite/PageWithoutContent.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/PageWithoutContent.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/PageWithoutContent.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/PageWithoutContent.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/Edit.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Edit.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Admin/Edit.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Edit.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Admin/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/Login.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Admin/RouteTemplate.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/RouteTemplate.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Admin/RouteTemplate.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Admin/RouteTemplate.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/AntiforgeryDefault.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/IgnoreAntiforgery.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Antiforgery/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ClassLibraryPages/Overriden.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/Auth.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/Auth.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Conventions/Auth.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/Auth.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/AnonymousViaModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Conventions/AuthFolder/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/CustomPageBase.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/CustomPageBase.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/CustomPageBase.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/CustomPageBase.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/Page.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/Page.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/Page.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/Page.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/PageWithModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomBaseType/_ViewStart.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomModelTypeModel.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/FileFromShared b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/FileFromShared similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/FileFromShared rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/FileFromShared diff --git a/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/HandlerWithParameter.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Localized/Page.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.fr-FR.resx b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.fr-FR.resx similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Localized/Page.fr-FR.resx rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/Page.fr-FR.resx diff --git a/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.fr-FR.resx b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.fr-FR.resx similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.fr-FR.resx rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Localized/PageWithModel.fr-FR.resx diff --git a/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Folder/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Folder/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Folder/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Folder/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Override/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Override/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Override/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/Nested/Override/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Namespace/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Namespace/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Namespace/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/NotTheRoot.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/NotTheRoot.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/NotTheRoot.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/NotTheRoot.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindFormFile.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesOnModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertiesWithSupportsGetOnModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/BindPropertyWithGet.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyAndArgumentBinding.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyAndArgumentBinding.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyAndArgumentBinding.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyAndArgumentBinding.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageModelWithPropertyBinding.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PagePropertyBinding.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PagePropertyBinding.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PagePropertyBinding.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PagePropertyBinding.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageWithPropertyAndArgumentBinding.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageWithPropertyAndArgumentBinding.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageWithPropertyAndArgumentBinding.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PageWithPropertyAndArgumentBinding.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/PolymorphicBinding.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/PropertyBinding/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/Redirect.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/Redirect.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/Redirect.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/Redirect.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromHandler.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromHandler.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromHandler.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromHandler.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectFromPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSelf.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSelf.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSelf.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSelf.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSibling.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSibling.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSibling.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/RedirectToSibling.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Redirects/SubDir/SubDirPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/SubDir/SubDirPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Redirects/SubDir/SubDirPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Redirects/SubDir/SubDirPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Routes/RouteUsingDefaultName.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/RouteUsingDefaultName.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Routes/RouteUsingDefaultName.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/RouteUsingDefaultName.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Routes/Sibling.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/Sibling.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Routes/Sibling.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/Sibling.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Routes/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Routes/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Routes/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/SearchInPages.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/SearchInPages.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/SearchInPages.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/SearchInPages.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Section/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Section/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Section/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Section/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Section/_Layout.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Section/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Section/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Section/_Layout.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Shared/Components/ViewData/Default.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/Components/ViewData/Default.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Shared/Components/ViewData/Default.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/Components/ViewData/Default.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Shared/_CustomBaseTypeLayout.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/_CustomBaseTypeLayout.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Shared/_CustomBaseTypeLayout.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/_CustomBaseTypeLayout.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Shared/_FileInShared.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/_FileInShared.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Shared/_FileInShared.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Shared/_FileInShared.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtAuthFilter.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ShortCircuitPageAtPageFilter.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/CrossPost.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/CrossPost.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/CrossPost.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/CrossPost.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/FormAction.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/FormAction.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/FormAction.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/FormAction.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PathTraversalLinks.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PathTraversalLinks.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/PathTraversalLinks.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PathTraversalLinks.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PostWithHandler.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PostWithHandler.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/PostWithHandler.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/PostWithHandler.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SelfPost.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SelfPost.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/SelfPost.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SelfPost.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SiblingLinks.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SiblingLinks.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/SiblingLinks.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SiblingLinks.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDir/SubDirPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDir/SubDirPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDir/SubDirPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDir/SubDirPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDirectoryLinks.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDirectoryLinks.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDirectoryLinks.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/SubDirectoryLinks.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TagHelper/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TagHelper/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TagHelper/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryUpdateModelPageModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/TryValidateModelPageModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Validation/PageHandlerWithValidation.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageHandlerWithValidation.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Validation/PageHandlerWithValidation.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageHandlerWithValidation.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/PageWithValidation.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/Validation/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/Validation/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/Validation/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPageWithoutModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPageWithoutModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPageWithoutModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataInPageWithoutModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_Layout.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataSetInViewStart/_ViewStart.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/ViewDataToViewComponentPage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/_Layout.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/_Layout.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewData/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewData/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewData/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewDataAvailableAfterHandlerExecuted.cshtml.cs diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewSearch/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/_Sibling.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/_Sibling.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/ViewSearch/_Sibling.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/ViewSearch/_Sibling.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/WithViewImport/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/WithViewImport/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewImport/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/Index.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/Index.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/WithViewStart/Index.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/Index.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/ViewStartAtRoot.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/ViewStartAtRoot.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/WithViewStart/ViewStartAtRoot.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/ViewStartAtRoot.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/WithViewStart/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/WithViewStart/_ViewStart.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/_Parent.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/_Parent.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/_Parent.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/_Parent.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Pages/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Pages/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Pages/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Pages/_ViewImports.cshtml diff --git a/test/WebSites/RazorPagesWebSite/PathSet.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/PathSet.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/PathSet.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/PathSet.cshtml diff --git a/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/PolymorphicModelBinder.cs diff --git a/test/WebSites/RazorPagesWebSite/Program.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Program.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Program.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Program.cs diff --git a/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj b/src/Mvc/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj similarity index 100% rename from test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj rename to src/Mvc/test/WebSites/RazorPagesWebSite/RazorPagesWebSite.csproj diff --git a/test/WebSites/RazorPagesWebSite/RedirectToController.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/RedirectToController.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/RedirectToController.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/RedirectToController.cshtml diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cs diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/RenderPartialWithoutModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/RouteData.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/RouteData.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/RouteData.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/RouteData.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Services/CustomService.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Services/CustomService.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Services/CustomService.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Services/CustomService.cs diff --git a/test/WebSites/RazorPagesWebSite/Show.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Show.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Show.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Show.cshtml diff --git a/test/WebSites/RazorPagesWebSite/SimpleForms.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/SimpleForms.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/SimpleForms.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/SimpleForms.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/Startup.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/Startup.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/Startup.cs diff --git a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs diff --git a/test/WebSites/RazorPagesWebSite/TagHelpers.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TagHelpers.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TagHelpers.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TagHelpers.cshtml diff --git a/test/WebSites/RazorPagesWebSite/TempData/SetMessageAndRedirect.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetMessageAndRedirect.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/SetMessageAndRedirect.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetMessageAndRedirect.cshtml diff --git a/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageAndRedirect.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageAndRedirect.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageAndRedirect.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageAndRedirect.cshtml diff --git a/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cs diff --git a/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/SetTempDataOnPageModelAndRedirect.cshtml diff --git a/test/WebSites/RazorPagesWebSite/TempData/ShowMessage.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/ShowMessage.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/ShowMessage.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/ShowMessage.cshtml diff --git a/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModel.cs b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModel.cs similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/TempDataPageModel.cs rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModel.cs diff --git a/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModelProperty.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModelProperty.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/TempData/TempDataPageModelProperty.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/TempData/TempDataPageModelProperty.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_FileInShared.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_FileInShared.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Views/Shared/_FileInShared.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_FileInShared.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_GlobalLayout.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_GlobalLayout.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Views/Shared/_GlobalLayout.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_GlobalLayout.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_PartialWithoutModel.cshtml diff --git a/test/WebSites/RazorPagesWebSite/Views/Shared/_Shared.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_Shared.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/Views/Shared/_Shared.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/Views/Shared/_Shared.cshtml diff --git a/test/WebSites/RazorPagesWebSite/_Root.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/_Root.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/_Root.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/_Root.cshtml diff --git a/test/WebSites/RazorPagesWebSite/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorPagesWebSite/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorPagesWebSite/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorPagesWebSite/_ViewStart.cshtml diff --git a/test/WebSites/RazorPagesWebSite/readme.md b/src/Mvc/test/WebSites/RazorPagesWebSite/readme.md similarity index 100% rename from test/WebSites/RazorPagesWebSite/readme.md rename to src/Mvc/test/WebSites/RazorPagesWebSite/readme.md diff --git a/test/WebSites/RazorWebSite/Components/ComponentForViewWithPaths.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/ComponentForViewWithPaths.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/ComponentForViewWithPaths.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/ComponentForViewWithPaths.cs diff --git a/test/WebSites/RazorWebSite/Components/ComponentThatSetsTitle.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/ComponentThatSetsTitle.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/ComponentThatSetsTitle.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/ComponentThatSetsTitle.cs diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithLayout.cs diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithRelativePath.cs diff --git a/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/ComponentWithViewStart.cs diff --git a/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs b/src/Mvc/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs similarity index 100% rename from test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs rename to src/Mvc/test/WebSites/RazorWebSite/Components/InheritingViewComponent.cs diff --git a/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/AddTagHelperComponentController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/BackSlashController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/BackSlashController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/DataAnnotationController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/DirectivesController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/DirectivesController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/EmbeddedViewsController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/ExpanderViewsController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/FlushPoint.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/FlushPoint.cs diff --git a/test/WebSites/RazorWebSite/Controllers/HtmlHelperOptionsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/HtmlHelperOptionsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/HtmlHelperOptionsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/HtmlHelperOptionsController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/NestedViewStartsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/NestedViewStartsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/NestedViewStartsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/NestedViewStartsController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/PartialViewEngineController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/PartialsWithLayoutController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/TagHelperComponentController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/TagHelperComponentController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/TagHelperComponentController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/TagHelperComponentController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/TemplateExpander.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/TemplateExpander.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/TemplateExpander.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/TemplateExpander.cs diff --git a/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/UpdateableFileProviderController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/UrlResolutionController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/UrlResolutionController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/UrlResolutionController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/UrlResolutionController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewEngineController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewNameSpecification_HomeController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/ViewWithPathsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewWithPathsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/ViewWithPathsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewWithPathsController.cs diff --git a/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs b/src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/Controllers/ViewsConsumingCompilationOptionsController.cs diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedShared/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/EmbeddedPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/EmbeddedPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/EmbeddedPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/EmbeddedPartial.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/Index.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/RelativeNonPath.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/RelativeNonPath.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/RelativeNonPath.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/RelativeNonPath.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/EmbeddedViews/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/EmbeddedResources/Views/Shared/_EmbeddedPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/Shared/_EmbeddedPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/EmbeddedResources/Views/Shared/_EmbeddedPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/EmbeddedResources/Views/Shared/_EmbeddedPartial.cshtml diff --git a/test/WebSites/RazorWebSite/Models/Address.cs b/src/Mvc/test/WebSites/RazorWebSite/Models/Address.cs similarity index 100% rename from test/WebSites/RazorWebSite/Models/Address.cs rename to src/Mvc/test/WebSites/RazorWebSite/Models/Address.cs diff --git a/test/WebSites/RazorWebSite/Models/EnumModel.cs b/src/Mvc/test/WebSites/RazorWebSite/Models/EnumModel.cs similarity index 100% rename from test/WebSites/RazorWebSite/Models/EnumModel.cs rename to src/Mvc/test/WebSites/RazorWebSite/Models/EnumModel.cs diff --git a/test/WebSites/RazorWebSite/Models/Person.cs b/src/Mvc/test/WebSites/RazorWebSite/Models/Person.cs similarity index 100% rename from test/WebSites/RazorWebSite/Models/Person.cs rename to src/Mvc/test/WebSites/RazorWebSite/Models/Person.cs diff --git a/test/WebSites/RazorWebSite/MyBasePage.cs b/src/Mvc/test/WebSites/RazorWebSite/MyBasePage.cs similarity index 100% rename from test/WebSites/RazorWebSite/MyBasePage.cs rename to src/Mvc/test/WebSites/RazorWebSite/MyBasePage.cs diff --git a/test/WebSites/RazorWebSite/NestedViewImportsController.cs b/src/Mvc/test/WebSites/RazorWebSite/NestedViewImportsController.cs similarity index 100% rename from test/WebSites/RazorWebSite/NestedViewImportsController.cs rename to src/Mvc/test/WebSites/RazorWebSite/NestedViewImportsController.cs diff --git a/test/WebSites/RazorWebSite/Pages/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Pages/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Pages/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Pages/Shared/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Pages/Shared/_SharedFromPages.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Pages/Shared/_SharedFromPages.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Pages/Shared/_SharedFromPages.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Pages/Shared/_SharedFromPages.cshtml diff --git a/test/WebSites/RazorWebSite/Program.cs b/src/Mvc/test/WebSites/RazorWebSite/Program.cs similarity index 100% rename from test/WebSites/RazorWebSite/Program.cs rename to src/Mvc/test/WebSites/RazorWebSite/Program.cs diff --git a/test/WebSites/RazorWebSite/RazorWebSite.csproj b/src/Mvc/test/WebSites/RazorWebSite/RazorWebSite.csproj similarity index 100% rename from test/WebSites/RazorWebSite/RazorWebSite.csproj rename to src/Mvc/test/WebSites/RazorWebSite/RazorWebSite.csproj diff --git a/test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx b/src/Mvc/test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx similarity index 100% rename from test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx rename to src/Mvc/test/WebSites/RazorWebSite/Resources/Models/ModelEnum.resx diff --git a/test/WebSites/RazorWebSite/Resources/SingleType.resx b/src/Mvc/test/WebSites/RazorWebSite/Resources/SingleType.resx similarity index 100% rename from test/WebSites/RazorWebSite/Resources/SingleType.resx rename to src/Mvc/test/WebSites/RazorWebSite/Resources/SingleType.resx diff --git a/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/BackSlashExpander.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/BackSlashExpander.cs diff --git a/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/FrameworkSpecificHelper.cs diff --git a/test/WebSites/RazorWebSite/Services/InjectedHelper.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/InjectedHelper.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/InjectedHelper.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/InjectedHelper.cs diff --git a/test/WebSites/RazorWebSite/Services/NonMainPageViewLocationExpander.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/NonMainPageViewLocationExpander.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/NonMainPageViewLocationExpander.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/NonMainPageViewLocationExpander.cs diff --git a/test/WebSites/RazorWebSite/Services/TaskReturningService.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/TaskReturningService.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/TaskReturningService.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/TaskReturningService.cs diff --git a/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/TestBodyTagHelperComponent.cs diff --git a/test/WebSites/RazorWebSite/Services/TestHeadTagHelperComponent.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/TestHeadTagHelperComponent.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/TestHeadTagHelperComponent.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/TestHeadTagHelperComponent.cs diff --git a/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs b/src/Mvc/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs similarity index 100% rename from test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs rename to src/Mvc/test/WebSites/RazorWebSite/Services/UpdateableFileProvider.cs diff --git a/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Shared-Views/ExpanderViews/_ExpanderPartial.cshtml diff --git a/test/WebSites/RazorWebSite/SingleType.cs b/src/Mvc/test/WebSites/RazorWebSite/SingleType.cs similarity index 100% rename from test/WebSites/RazorWebSite/SingleType.cs rename to src/Mvc/test/WebSites/RazorWebSite/SingleType.cs diff --git a/test/WebSites/RazorWebSite/Startup.cs b/src/Mvc/test/WebSites/RazorWebSite/Startup.cs similarity index 100% rename from test/WebSites/RazorWebSite/Startup.cs rename to src/Mvc/test/WebSites/RazorWebSite/Startup.cs diff --git a/test/WebSites/RazorWebSite/StartupDataAnnotations.cs b/src/Mvc/test/WebSites/RazorWebSite/StartupDataAnnotations.cs similarity index 100% rename from test/WebSites/RazorWebSite/StartupDataAnnotations.cs rename to src/Mvc/test/WebSites/RazorWebSite/StartupDataAnnotations.cs diff --git a/test/WebSites/RazorWebSite/Views/AddTagHelperComponent/AddComponent.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/AddTagHelperComponent/AddComponent.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/AddTagHelperComponent/AddComponent.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/AddTagHelperComponent/AddComponent.cshtml diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/BackSlashView.cshtml diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/_BackSlashPartial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/BackSlash/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/ViewInheritsBasePageFromViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/Scoped/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/ViewInheritsInjectAndUsingsFromViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Directives/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Directives/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Directives/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Directives/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Enum/Enum.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ExpanderViews/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithFlushBeforeLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithNestedLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithPartialsAndViewComponents.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithSectionInvokedViaRenderSectionAsync.cshtml diff --git a/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/FlushPoint/PageWithoutLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInPartialView.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInPartialView.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInPartialView.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInPartialView.cshtml diff --git a/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInView.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInView.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInView.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/HtmlHelperOptionsDefaultsInView.cshtml diff --git a/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInPartialView.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInPartialView.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInPartialView.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInPartialView.cshtml diff --git a/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInView.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInView.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInView.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/HtmlHelperOptions/OverrideAppWideDefaultsInView.cshtml diff --git a/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewComponent.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewComponent.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewComponent.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewComponent.cshtml diff --git a/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/InheritingInherits/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/Nested/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewImports/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewImports/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewImports/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/NestedViewStarts/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/NestedViewStarts/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/NestedViewStarts/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/NestedViewStarts/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialMissingSection.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialMissingSection.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialMissingSection.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialMissingSection.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialWithModel.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialWithModel.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialWithModel.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/PartialWithModel.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewPartialMissingSection.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewPartialMissingSection.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewPartialMissingSection.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewPartialMissingSection.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithDataFromController.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithDataFromController.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithDataFromController.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithDataFromController.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithFullPath.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithFullPath.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithFullPath.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithFullPath.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithNestedLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithNestedLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithNestedLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithNestedLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithoutLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithoutLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithoutLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialViewEngine/ViewWithoutLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/LayoutForViewStartWithLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatDoesNotSpecifyLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialThatSpecifiesLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaPartialAsync.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/PartialsRenderedViaRenderPartial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/PartialsWithLayout/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentForViewWithPaths/Default.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentThatSetsTitle/Default.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentThatSetsTitle/Default.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentThatSetsTitle/Default.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentThatSetsTitle/Default.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithLayout/Default.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithRelativePath.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/Default.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/Components/ComponentWithViewStart/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/DisplayTemplates/Name.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_ComponentLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithFlush.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithPartialAndFlush.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionAsync.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithRenderSectionOnly.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithTitle.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithTitle.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_LayoutWithTitle.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_LayoutWithTitle.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_NestedLayoutWithFlush.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_PartialLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_PartialLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_PartialThatSetsTitle.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialThatSetsTitle.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_PartialThatSetsTitle.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialThatSetsTitle.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialWithFlush.cshtml diff --git a/test/WebSites/RazorWebSite/Views/Shared/_PartialWithModelFromEnumerable.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialWithModelFromEnumerable.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/Shared/_PartialWithModelFromEnumerable.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/Shared/_PartialWithModelFromEnumerable.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TagHelperComponent/Body.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TagHelperComponent/Head.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TagHelperComponent/Head.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TagHelperComponent/Head.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TagHelperComponent/Head.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/ViewWithLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/ViewWithLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/ViewWithLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/ViewWithLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/_LanguageLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/_LanguageLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/_LanguageLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/_LanguageLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/en-GB/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/en-GB/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/en-GB/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/en-GB/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/fr/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_LanguageLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_LanguageLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_LanguageLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_LanguageLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/TemplateExpander/fr/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/UrlResolution/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/UrlResolution/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/UrlResolution/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/UrlResolution/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/SearchInPages.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/SearchInPages.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/SearchInPages.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/SearchInPages.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithComponentThatHasViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithDataFromController.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithDataFromController.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithDataFromController.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithDataFromController.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithFullPath.rzr diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithNestedLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartialTakingModelFromIEnumerable.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartialTakingModelFromIEnumerable.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartialTakingModelFromIEnumerable.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithPartialTakingModelFromIEnumerable.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithRelativePath.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithTitle.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithoutLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithoutLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithoutLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/ViewWithoutLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewEngine/_NestedLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/_NestedLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewEngine/_NestedLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewEngine/_NestedLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInPage.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/LayoutSpecifiedWithPartialPathInViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/NonSharedPartial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/NonSharedPartial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/NonSharedPartial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/NonSharedPartial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/PageWithNonPartialLayoutPath.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/PageWithNonPartialLayoutPath.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/PageWithNonPartialLayoutPath.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/PageWithNonPartialLayoutPath.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/ViewWithPartials.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/ViewWithPartials.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/ViewWithPartials.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/ViewWithPartials.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_NonSharedLayout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_NonSharedLayout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_NonSharedLayout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_NonSharedLayout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewNameSpecification_Home/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Layout.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Layout.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewWithPaths/_Layout.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Layout.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewWithPaths/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewWithPaths/_ViewStart.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_ViewStart.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewWithPaths/_ViewStart.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewWithPaths/_ViewStart.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/Index.cshtml diff --git a/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/ViewsConsumingCompilationOptions/_Partial.cshtml diff --git a/test/WebSites/RazorWebSite/Views/_ViewImports.cshtml b/src/Mvc/test/WebSites/RazorWebSite/Views/_ViewImports.cshtml similarity index 100% rename from test/WebSites/RazorWebSite/Views/_ViewImports.cshtml rename to src/Mvc/test/WebSites/RazorWebSite/Views/_ViewImports.cshtml diff --git a/test/WebSites/RazorWebSite/readme.md b/src/Mvc/test/WebSites/RazorWebSite/readme.md similarity index 100% rename from test/WebSites/RazorWebSite/readme.md rename to src/Mvc/test/WebSites/RazorWebSite/readme.md diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/LG3Controller.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/Pages/LGAreaPage.cshtml.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Admin/UserManagementController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/UserManagementController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Admin/UserManagementController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Admin/UserManagementController.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Order/OrderController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Order/OrderController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Order/OrderController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Order/OrderController.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Travel/FlightController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/FlightController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Travel/FlightController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/FlightController.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Travel/HomeController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/HomeController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Travel/HomeController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/HomeController.cs diff --git a/test/WebSites/RoutingWebSite/Areas/Travel/RailController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/RailController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Areas/Travel/RailController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Areas/Travel/RailController.cs diff --git a/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs b/src/Mvc/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs similarity index 100% rename from test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs rename to src/Mvc/test/WebSites/RoutingWebSite/ControllerRouteTokenTransformerConvention.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/BanksController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/BanksController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/BanksController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/BanksController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/BlogController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/BlogController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/BlogController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/BlogController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/CompanyController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/CompanyController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/CompanyController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/CompanyController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/ConventionalTransformerController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/DataTokensController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/DefaultValuesController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/EmployeeController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/EndpointRoutingController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/FriendsController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/FriendsController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/FriendsController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/FriendsController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/LG1Controller.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/LG2Controller.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/MapsController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/MapsController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/MapsController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/MapsController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/OrderController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/OrderController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/OrderController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/OrderController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/PageRouteController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/ParameterTransformerController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/RouteDataController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/RoutingController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/RoutingController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/StoreController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/StoreController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/StoreController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/StoreController.cs diff --git a/test/WebSites/RoutingWebSite/Controllers/TeamController.cs b/src/Mvc/test/WebSites/RoutingWebSite/Controllers/TeamController.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Controllers/TeamController.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Controllers/TeamController.cs diff --git a/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs b/src/Mvc/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs similarity index 100% rename from test/WebSites/RoutingWebSite/HttpMergeAttribute.cs rename to src/Mvc/test/WebSites/RoutingWebSite/HttpMergeAttribute.cs diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml diff --git a/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs b/src/Mvc/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/LGAnotherPage.cshtml.cs diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/LGPage.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml diff --git a/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs b/src/Mvc/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/LGPage.cshtml.cs diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/Index.cshtml diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/PageWithConfiguredRoute.cshtml diff --git a/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml b/src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml similarity index 100% rename from test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml rename to src/Mvc/test/WebSites/RoutingWebSite/Pages/PageRouteTransformer/TestPage.cshtml diff --git a/test/WebSites/RoutingWebSite/Program.cs b/src/Mvc/test/WebSites/RoutingWebSite/Program.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Program.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Program.cs diff --git a/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs b/src/Mvc/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs similarity index 100% rename from test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs rename to src/Mvc/test/WebSites/RoutingWebSite/RemoveControllerActionDescriptorProvider.cs diff --git a/test/WebSites/RoutingWebSite/RoutingWebSite.csproj b/src/Mvc/test/WebSites/RoutingWebSite/RoutingWebSite.csproj similarity index 100% rename from test/WebSites/RoutingWebSite/RoutingWebSite.csproj rename to src/Mvc/test/WebSites/RoutingWebSite/RoutingWebSite.csproj diff --git a/test/WebSites/RoutingWebSite/Startup.cs b/src/Mvc/test/WebSites/RoutingWebSite/Startup.cs similarity index 100% rename from test/WebSites/RoutingWebSite/Startup.cs rename to src/Mvc/test/WebSites/RoutingWebSite/Startup.cs diff --git a/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs b/src/Mvc/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs similarity index 100% rename from test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs rename to src/Mvc/test/WebSites/RoutingWebSite/StartupForLinkGenerator.cs diff --git a/test/WebSites/RoutingWebSite/StartupWith21Compat.cs b/src/Mvc/test/WebSites/RoutingWebSite/StartupWith21Compat.cs similarity index 100% rename from test/WebSites/RoutingWebSite/StartupWith21Compat.cs rename to src/Mvc/test/WebSites/RoutingWebSite/StartupWith21Compat.cs diff --git a/test/WebSites/RoutingWebSite/TestParameterTransformer.cs b/src/Mvc/test/WebSites/RoutingWebSite/TestParameterTransformer.cs similarity index 100% rename from test/WebSites/RoutingWebSite/TestParameterTransformer.cs rename to src/Mvc/test/WebSites/RoutingWebSite/TestParameterTransformer.cs diff --git a/test/WebSites/RoutingWebSite/readme.md b/src/Mvc/test/WebSites/RoutingWebSite/readme.md similarity index 100% rename from test/WebSites/RoutingWebSite/readme.md rename to src/Mvc/test/WebSites/RoutingWebSite/readme.md diff --git a/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs b/src/Mvc/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Controllers/AdministrationController.cs diff --git a/test/WebSites/SecurityWebSite/Controllers/AntiforgeryController.cs b/src/Mvc/test/WebSites/SecurityWebSite/Controllers/AntiforgeryController.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Controllers/AntiforgeryController.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Controllers/AntiforgeryController.cs diff --git a/test/WebSites/SecurityWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/SecurityWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/SecurityWebSite/Controllers/IgnoreAntiforgeryController.cs b/src/Mvc/test/WebSites/SecurityWebSite/Controllers/IgnoreAntiforgeryController.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Controllers/IgnoreAntiforgeryController.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Controllers/IgnoreAntiforgeryController.cs diff --git a/test/WebSites/SecurityWebSite/CountingPolicyEvaluator.cs b/src/Mvc/test/WebSites/SecurityWebSite/CountingPolicyEvaluator.cs similarity index 100% rename from test/WebSites/SecurityWebSite/CountingPolicyEvaluator.cs rename to src/Mvc/test/WebSites/SecurityWebSite/CountingPolicyEvaluator.cs diff --git a/test/WebSites/SecurityWebSite/Program.cs b/src/Mvc/test/WebSites/SecurityWebSite/Program.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Program.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Program.cs diff --git a/test/WebSites/SecurityWebSite/SecurityWebSite.csproj b/src/Mvc/test/WebSites/SecurityWebSite/SecurityWebSite.csproj similarity index 100% rename from test/WebSites/SecurityWebSite/SecurityWebSite.csproj rename to src/Mvc/test/WebSites/SecurityWebSite/SecurityWebSite.csproj diff --git a/test/WebSites/SecurityWebSite/Startup.cs b/src/Mvc/test/WebSites/SecurityWebSite/Startup.cs similarity index 100% rename from test/WebSites/SecurityWebSite/Startup.cs rename to src/Mvc/test/WebSites/SecurityWebSite/Startup.cs diff --git a/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs b/src/Mvc/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs similarity index 100% rename from test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs rename to src/Mvc/test/WebSites/SecurityWebSite/StartupWith20CompatAndGlobalDenyAnonymousFilter.cs diff --git a/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs b/src/Mvc/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs similarity index 100% rename from test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs rename to src/Mvc/test/WebSites/SecurityWebSite/StartupWithGlobalDenyAnonymousFilter.cs diff --git a/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml b/src/Mvc/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/SecurityWebSite/Views/Home/Index.cshtml rename to src/Mvc/test/WebSites/SecurityWebSite/Views/Home/Index.cshtml diff --git a/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/SecurityWebSite/Views/Shared/_Layout.cshtml diff --git a/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml b/src/Mvc/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml similarity index 100% rename from test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml rename to src/Mvc/test/WebSites/SecurityWebSite/Views/_ViewImports.cshtml diff --git a/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml b/src/Mvc/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml similarity index 100% rename from test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml rename to src/Mvc/test/WebSites/SecurityWebSite/Views/_ViewStart.cshtml diff --git a/test/WebSites/SecurityWebSite/appsettings.json b/src/Mvc/test/WebSites/SecurityWebSite/appsettings.json similarity index 100% rename from test/WebSites/SecurityWebSite/appsettings.json rename to src/Mvc/test/WebSites/SecurityWebSite/appsettings.json diff --git a/test/WebSites/SimpleWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/SimpleWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/SimpleWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/SimpleWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/SimpleWebSite/SimpleWebSite.csproj b/src/Mvc/test/WebSites/SimpleWebSite/SimpleWebSite.csproj similarity index 100% rename from test/WebSites/SimpleWebSite/SimpleWebSite.csproj rename to src/Mvc/test/WebSites/SimpleWebSite/SimpleWebSite.csproj diff --git a/test/WebSites/SimpleWebSite/Startup.cs b/src/Mvc/test/WebSites/SimpleWebSite/Startup.cs similarity index 100% rename from test/WebSites/SimpleWebSite/Startup.cs rename to src/Mvc/test/WebSites/SimpleWebSite/Startup.cs diff --git a/test/WebSites/SimpleWebSite/readme.md b/src/Mvc/test/WebSites/SimpleWebSite/readme.md similarity index 100% rename from test/WebSites/SimpleWebSite/readme.md rename to src/Mvc/test/WebSites/SimpleWebSite/readme.md diff --git a/test/WebSites/TagHelpersWebSite/Components/CopyrightViewComponent.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Components/CopyrightViewComponent.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Components/CopyrightViewComponent.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Components/CopyrightViewComponent.cs diff --git a/test/WebSites/TagHelpersWebSite/Components/DanViewComponent.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Components/DanViewComponent.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Components/DanViewComponent.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Components/DanViewComponent.cs diff --git a/test/WebSites/TagHelpersWebSite/Components/GenericViewComponent.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Components/GenericViewComponent.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Components/GenericViewComponent.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Components/GenericViewComponent.cs diff --git a/test/WebSites/TagHelpersWebSite/Components/JacketColor.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Components/JacketColor.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Components/JacketColor.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Components/JacketColor.cs diff --git a/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/EmployeeController.cs diff --git a/test/WebSites/TagHelpersWebSite/Controllers/EncodersController.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/EncodersController.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Controllers/EncodersController.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/EncodersController.cs diff --git a/test/WebSites/TagHelpersWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/TagHelpersWebSite/Controllers/RemoveDefaultInheritedTagHelpersController.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/RemoveDefaultInheritedTagHelpersController.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Controllers/RemoveDefaultInheritedTagHelpersController.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Controllers/RemoveDefaultInheritedTagHelpersController.cs diff --git a/test/WebSites/TagHelpersWebSite/Models/Employee.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Models/Employee.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Models/Employee.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Models/Employee.cs diff --git a/test/WebSites/TagHelpersWebSite/Models/WebsiteContext.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Models/WebsiteContext.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Models/WebsiteContext.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Models/WebsiteContext.cs diff --git a/test/WebSites/TagHelpersWebSite/Startup.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/Startup.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/Startup.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/Startup.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/ATagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/AddProcessedAttributeTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/AddProcessedAttributeTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/AddProcessedAttributeTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/AddProcessedAttributeTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/AutoLinkerTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/BoldTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/BoldTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/BoldTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/BoldTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/ConditionTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/CustomEncoderTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/CustomEncoderTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/CustomEncoderTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/CustomEncoderTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/DefaultEncoderTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/DefaultEncoderTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/DefaultEncoderTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/DefaultEncoderTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/DictionaryPrefixTestTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/DictionaryPrefixTestTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/DictionaryPrefixTestTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/DictionaryPrefixTestTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewImportsTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewImportsTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewImportsTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/NestedViewImportsTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/NullEncoderTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/NullEncoderTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/NullEncoderTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/NullEncoderTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/PrettyTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/RootViewStartTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/SurroundTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/SurroundTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/SurroundTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/SurroundTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/TagCloudViewComponentTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpers/WebsiteInformationTagHelper.cs diff --git a/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj b/src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj similarity index 100% rename from test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj rename to src/Mvc/test/WebSites/TagHelpersWebSite/TagHelpersWebSite.csproj diff --git a/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/Create.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/Details.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Employee/DuplicateAntiforgeryTokenRegistration.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/DuplicateAntiforgeryTokenRegistration.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Employee/DuplicateAntiforgeryTokenRegistration.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Employee/DuplicateAntiforgeryTokenRegistration.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/CustomEncoder.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/CustomEncoder.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/CustomEncoder.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/CustomEncoder.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/Index.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/Index.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/Index.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/Index.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/NullEncoder.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/NullEncoder.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/NullEncoder.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/NullEncoder.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/ThreeEncoders.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/ThreeEncoders.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/ThreeEncoders.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/ThreeEncoders.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/TwoEncoders.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/TwoEncoders.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/TwoEncoders.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/TwoEncoders.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/_Layout.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_Layout.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/_Layout.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_Layout.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewStart.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewStart.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewStart.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Encoders/_ViewStart.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/About.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/About.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/About.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/About.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/Help.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/Help.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/Help.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/Help.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/Index.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/NestedViewImportsTagHelper.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/NestedViewImportsTagHelper.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/NestedViewImportsTagHelper.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/NestedViewImportsTagHelper.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/UnboundDynamicAttributes.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/UnboundDynamicAttributes.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/UnboundDynamicAttributes.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/UnboundDynamicAttributes.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/ViewComponentTagHelpers.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/ViewComponentTagHelpers.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/ViewComponentTagHelpers.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/ViewComponentTagHelpers.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Home/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Home/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Home/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/InheritedTagHelperPrefix.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/InheritedTagHelperPrefix.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/InheritedTagHelperPrefix.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/InheritedTagHelperPrefix.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedInheritedTagHelperPrefix.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedInheritedTagHelperPrefix.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedInheritedTagHelperPrefix.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedInheritedTagHelperPrefix.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedOverriddenTagHelperPrefix.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedOverriddenTagHelperPrefix.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedOverriddenTagHelperPrefix.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/NestedOverriddenTagHelperPrefix.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/NestedInheritedTagHelperPrefix/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/OverriddenTagHelperPrefix.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/OverriddenTagHelperPrefix.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/OverriddenTagHelperPrefix.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/OverriddenTagHelperPrefix.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewStart.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewStart.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewStart.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/InheritedTagHelperPrefix/_ViewStart.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/Index.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/Index.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/Index.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/Index.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveDefaultInheritedTagHelpers/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/ViewWithInheritedRemoveTagHelper.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewStart.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewStart.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewStart.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/RemoveInheritedTagHelpers/_ViewStart.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Copyright/Default.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Copyright/Default.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/Components/Copyright/Default.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Copyright/Default.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Dan/Default.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Dan/Default.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/Components/Dan/Default.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Dan/Default.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Generic/Default.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Generic/Default.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/Components/Generic/Default.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/Components/Generic/Default.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/ViewWithLayoutAndNestedTagHelper.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/ViewWithLayoutAndNestedTagHelper.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/ViewWithLayoutAndNestedTagHelper.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/ViewWithLayoutAndNestedTagHelper.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/_Layout.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/_Layout.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/_Layout.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/_Layout.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/Shared/_LayoutWithRootTagHelper.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/_LayoutWithRootTagHelper.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/Shared/_LayoutWithRootTagHelper.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/Shared/_LayoutWithRootTagHelper.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/_ViewImports.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/_ViewImports.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/_ViewImports.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/_ViewImports.cshtml diff --git a/test/WebSites/TagHelpersWebSite/Views/_ViewStart.cshtml b/src/Mvc/test/WebSites/TagHelpersWebSite/Views/_ViewStart.cshtml similarity index 100% rename from test/WebSites/TagHelpersWebSite/Views/_ViewStart.cshtml rename to src/Mvc/test/WebSites/TagHelpersWebSite/Views/_ViewStart.cshtml diff --git a/test/WebSites/TagHelpersWebSite/readme.md b/src/Mvc/test/WebSites/TagHelpersWebSite/readme.md similarity index 100% rename from test/WebSites/TagHelpersWebSite/readme.md rename to src/Mvc/test/WebSites/TagHelpersWebSite/readme.md diff --git a/test/WebSites/VersioningWebSite/Controllers/AddressController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/AddressController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/AddressController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/AddressController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/BooksController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/BooksController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/BooksController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/BooksController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/CustomersController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/CustomersController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/CustomersController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/CustomersController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/ItemsController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/ItemsController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/ItemsController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/ItemsController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/ItemsV2Controller.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/ItemsV2Controller.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/ItemsV2Controller.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/ItemsV2Controller.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/MoviesController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/MoviesController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/MoviesController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/MoviesController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/MoviesV2Controller.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/MoviesV2Controller.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/MoviesV2Controller.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/MoviesV2Controller.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/PetsController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/PetsController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/PetsController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/PetsController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/RoutingController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/RoutingController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/TicketsController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/TicketsController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/TicketsController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/TicketsController.cs diff --git a/test/WebSites/VersioningWebSite/Controllers/VouchersController.cs b/src/Mvc/test/WebSites/VersioningWebSite/Controllers/VouchersController.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Controllers/VouchersController.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Controllers/VouchersController.cs diff --git a/test/WebSites/VersioningWebSite/Program.cs b/src/Mvc/test/WebSites/VersioningWebSite/Program.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Program.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Program.cs diff --git a/test/WebSites/VersioningWebSite/Startup.cs b/src/Mvc/test/WebSites/VersioningWebSite/Startup.cs similarity index 100% rename from test/WebSites/VersioningWebSite/Startup.cs rename to src/Mvc/test/WebSites/VersioningWebSite/Startup.cs diff --git a/test/WebSites/VersioningWebSite/StartupWith21Compat.cs b/src/Mvc/test/WebSites/VersioningWebSite/StartupWith21Compat.cs similarity index 100% rename from test/WebSites/VersioningWebSite/StartupWith21Compat.cs rename to src/Mvc/test/WebSites/VersioningWebSite/StartupWith21Compat.cs diff --git a/test/WebSites/VersioningWebSite/VersionAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionDeleteAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersionGetAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionGetAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionGetAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionGetAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersionPostAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionPostAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionPostAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionPostAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersionPutAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionPutAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionPutAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionPutAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersionRangeValidator.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionRangeValidator.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionRangeValidator.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionRangeValidator.cs diff --git a/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs b/src/Mvc/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs similarity index 100% rename from test/WebSites/VersioningWebSite/VersionRouteAttribute.cs rename to src/Mvc/test/WebSites/VersioningWebSite/VersionRouteAttribute.cs diff --git a/test/WebSites/VersioningWebSite/VersioningWebSite.csproj b/src/Mvc/test/WebSites/VersioningWebSite/VersioningWebSite.csproj similarity index 100% rename from test/WebSites/VersioningWebSite/VersioningWebSite.csproj rename to src/Mvc/test/WebSites/VersioningWebSite/VersioningWebSite.csproj diff --git a/test/WebSites/VersioningWebSite/readme.md b/src/Mvc/test/WebSites/VersioningWebSite/readme.md similarity index 100% rename from test/WebSites/VersioningWebSite/readme.md rename to src/Mvc/test/WebSites/VersioningWebSite/readme.md diff --git a/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/ActionSelectionFilter.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionResults/ActionResultController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionResults/ActionResultController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionResults/ActionResultController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionResults/ActionResultController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/EnumParameterOverloadsController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/EnumParameterOverloadsController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/EnumParameterOverloadsController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/EnumParameterOverloadsController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/ParameterAttributeController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/ParameterAttributeController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/ParameterAttributeController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/ParameterAttributeController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/TestController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/TestController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/TestController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/Legacy/TestController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsActionNameController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsActionNameController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsActionNameController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsActionNameController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsDefaultPostController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsDefaultPostController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsDefaultPostController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsDefaultPostController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsVerbOverrideController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsVerbOverrideController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsVerbOverrideController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ActionSelection/WebAPIActionConventionsVerbOverrideController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/BasicApiController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpRequestMessage/HttpRequestMessageController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpResponseExceptionController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpResponseExceptionController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/HttpResponseExceptionController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/HttpResponseExceptionController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/MvcController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/MvcController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/MvcController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/MvcController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Controllers/ParameterBinding/EmployeesController.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ParameterBinding/EmployeesController.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Controllers/ParameterBinding/EmployeesController.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Controllers/ParameterBinding/EmployeesController.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Models/Employee.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/Employee.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Models/Employee.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/Employee.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Models/User.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/User.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Models/User.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/User.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Models/UserAddress.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/UserAddress.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Models/UserAddress.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/UserAddress.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Models/UserKind.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/UserKind.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Models/UserKind.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Models/UserKind.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/Startup.cs b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/Startup.cs similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/Startup.cs rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/Startup.cs diff --git a/test/WebSites/WebApiCompatShimWebSite/WebApiCompatShimWebSite.csproj b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/WebApiCompatShimWebSite.csproj similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/WebApiCompatShimWebSite.csproj rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/WebApiCompatShimWebSite.csproj diff --git a/test/WebSites/WebApiCompatShimWebSite/readme.md b/src/Mvc/test/WebSites/WebApiCompatShimWebSite/readme.md similarity index 100% rename from test/WebSites/WebApiCompatShimWebSite/readme.md rename to src/Mvc/test/WebSites/WebApiCompatShimWebSite/readme.md diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/HomeController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/IEnumerableController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/IQueryableController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/SerializableErrorController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/ValidationController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/ValidationController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/ValidationController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/ValidationController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlApiControllerBase.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlDataContractApiController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Controllers/XmlSerializedApiController.cs diff --git a/test/WebSites/XmlFormattersWebSite/Models/Address.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Address.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Models/Address.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Address.cs diff --git a/test/WebSites/XmlFormattersWebSite/Models/DummyClass.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Models/DummyClass.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Models/DummyClass.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Models/DummyClass.cs diff --git a/test/WebSites/XmlFormattersWebSite/Models/Employee.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Employee.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Models/Employee.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Employee.cs diff --git a/test/WebSites/XmlFormattersWebSite/Models/Person.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Person.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Models/Person.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Person.cs diff --git a/test/WebSites/XmlFormattersWebSite/Models/Store.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Store.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Models/Store.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Models/Store.cs diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/PersonWrapper.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapper.cs diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapperProvider.cs diff --git a/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/PersonWrapperProviderFactory.cs diff --git a/test/WebSites/XmlFormattersWebSite/Startup.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/Startup.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/Startup.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/Startup.cs diff --git a/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs b/src/Mvc/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs similarity index 100% rename from test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs rename to src/Mvc/test/WebSites/XmlFormattersWebSite/StartupWith21Compat.cs diff --git a/test/WebSites/XmlFormattersWebSite/XmlFormattersWebSite.csproj b/src/Mvc/test/WebSites/XmlFormattersWebSite/XmlFormattersWebSite.csproj similarity index 100% rename from test/WebSites/XmlFormattersWebSite/XmlFormattersWebSite.csproj rename to src/Mvc/test/WebSites/XmlFormattersWebSite/XmlFormattersWebSite.csproj diff --git a/test/WebSites/XmlFormattersWebSite/readme.md b/src/Mvc/test/WebSites/XmlFormattersWebSite/readme.md similarity index 100% rename from test/WebSites/XmlFormattersWebSite/readme.md rename to src/Mvc/test/WebSites/XmlFormattersWebSite/readme.md diff --git a/version.props b/src/Mvc/version.props similarity index 100% rename from version.props rename to src/Mvc/version.props